summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--PREUPLOAD.cfg2
-rw-r--r--apex/jobscheduler/service/aconfig/app_idle.aconfig7
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java55
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobStore.java4
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java31
-rw-r--r--apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java58
-rw-r--r--core/api/current.txt15
-rw-r--r--core/api/system-current.txt23
-rw-r--r--core/java/android/app/ApplicationPackageManager.java28
-rw-r--r--core/java/android/app/Notification.java167
-rw-r--r--core/java/android/app/PropertyInvalidatedCache.java208
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java29
-rw-r--r--core/java/android/app/admin/IDevicePolicyManager.aidl1
-rw-r--r--core/java/android/companion/CompanionDeviceService.java6
-rw-r--r--core/java/android/content/Context.java14
-rw-r--r--core/java/android/content/pm/ApplicationInfo.java105
-rw-r--r--core/java/android/content/pm/IPackageManager.aidl8
-rw-r--r--core/java/android/content/pm/PackageManager.java63
-rw-r--r--core/java/android/content/pm/multiuser.aconfig22
-rw-r--r--core/java/android/hardware/contexthub/HubEndpoint.java51
-rw-r--r--core/java/android/hardware/contexthub/IContextHubEndpointDiscoveryCallback.aidl37
-rw-r--r--core/java/android/hardware/contexthub/IHubEndpointDiscoveryCallback.java49
-rw-r--r--core/java/android/hardware/contexthub/IHubEndpointLifecycleCallback.java24
-rw-r--r--core/java/android/hardware/location/ContextHubManager.java194
-rw-r--r--core/java/android/hardware/location/IContextHubService.aidl13
-rw-r--r--core/java/android/net/vcn/VcnGatewayConnectionConfig.java16
-rw-r--r--core/java/android/os/AggregateBatteryConsumer.java14
-rw-r--r--core/java/android/os/BatteryConsumer.java80
-rw-r--r--core/java/android/os/BatteryManager.java84
-rw-r--r--core/java/android/os/BatteryUsageStats.java32
-rw-r--r--core/java/android/os/BatteryUsageStatsQuery.java32
-rw-r--r--core/java/android/os/CombinedMessageQueue/MessageQueue.java4
-rw-r--r--core/java/android/os/CpuHeadroomParams.java70
-rw-r--r--core/java/android/os/CpuHeadroomParamsInternal.aidl2
-rw-r--r--core/java/android/os/GpuHeadroomParams.java44
-rw-r--r--core/java/android/os/GpuHeadroomParamsInternal.aidl1
-rw-r--r--core/java/android/os/IHintManager.aidl6
-rw-r--r--core/java/android/os/OWNERS2
-rw-r--r--core/java/android/os/PowerComponents.java70
-rw-r--r--core/java/android/os/flags.aconfig9
-rw-r--r--core/java/android/os/health/SystemHealthManager.java28
-rw-r--r--core/java/android/permission/flags.aconfig13
-rw-r--r--core/java/android/security/responsible_apis_flags.aconfig13
-rw-r--r--core/java/android/service/quickaccesswallet/flags.aconfig7
-rw-r--r--core/java/android/view/ScrollCaptureSearchResults.java77
-rw-r--r--core/java/android/view/ViewGroup.java37
-rw-r--r--core/java/android/view/ViewRootImpl.java18
-rw-r--r--core/java/android/view/accessibility/AccessibilityEvent.java12
-rw-r--r--core/java/android/view/flags/scroll_capture.aconfig13
-rw-r--r--core/java/android/widget/ProgressBar.java17
-rw-r--r--core/java/android/widget/RemoteViews.java14
-rw-r--r--core/java/android/window/WindowContext.java17
-rw-r--r--core/java/com/android/internal/content/NativeLibraryHelper.java53
-rw-r--r--core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java19
-rw-r--r--core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java4
-rw-r--r--core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java8
-rw-r--r--core/java/com/android/server/pm/pkg/AndroidPackage.java8
-rw-r--r--core/jni/android_content_res_ApkAssets.cpp4
-rw-r--r--core/jni/com_android_internal_content_NativeLibraryHelper.cpp210
-rw-r--r--core/proto/android/content/package_item_info.proto1
-rw-r--r--core/res/AndroidManifest.xml17
-rw-r--r--core/res/res/drawable-watch-v36/dialog_alert_button_background_negative.xml2
-rw-r--r--core/res/res/layout-watch-v36/alert_dialog_icon_button_wear_material3.xml123
-rw-r--r--core/res/res/layout-watch-v36/alert_dialog_material.xml24
-rw-r--r--core/res/res/layout/notification_2025_template_collapsed_base.xml2
-rw-r--r--core/res/res/layout/notification_2025_template_collapsed_media.xml197
-rw-r--r--core/res/res/layout/notification_2025_template_collapsed_messaging.xml220
-rw-r--r--core/res/res/layout/notification_2025_text.xml (renamed from core/res/res/layout/notification_2025_template_text.xml)0
-rw-r--r--core/res/res/layout/notification_template_header.xml33
-rw-r--r--core/res/res/layout/notification_template_material_base.xml40
-rw-r--r--core/res/res/layout/notification_template_material_media.xml8
-rw-r--r--core/res/res/layout/notification_template_material_messaging.xml6
-rw-r--r--core/res/res/values-watch-v36/config.xml2
-rw-r--r--core/res/res/values-watch-v36/styles_material.xml6
-rw-r--r--core/res/res/values/attrs_manifest.xml8
-rw-r--r--core/res/res/values/config.xml6
-rw-r--r--core/res/res/values/strings.xml15
-rw-r--r--core/res/res/values/symbols.xml10
-rw-r--r--core/tests/batterystatstests/BatteryStatsViewer/res/drawable-mdpi/border_ltr.9.pngbin0 -> 114 bytes
-rw-r--r--core/tests/batterystatstests/BatteryStatsViewer/res/drawable-mdpi/border_tr.9.pngbin0 -> 108 bytes
-rw-r--r--core/tests/batterystatstests/BatteryStatsViewer/res/drawable/power_other_24.xml10
-rw-r--r--core/tests/batterystatstests/BatteryStatsViewer/res/drawable/screen_off_24.xml10
-rw-r--r--core/tests/batterystatstests/BatteryStatsViewer/res/drawable/screen_on_24.xml10
-rw-r--r--core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_consumer_entry_layout.xml158
-rw-r--r--core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_consumer_picker_layout.xml32
-rw-r--r--core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_consumer_slices_layout.xml87
-rw-r--r--core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_stats_viewer_layout.xml5
-rw-r--r--core/tests/batterystatstests/BatteryStatsViewer/res/values/colors.xml1
-rw-r--r--core/tests/batterystatstests/BatteryStatsViewer/res/values/styles.xml29
-rw-r--r--core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerData.java233
-rw-r--r--core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerInfoHelper.java21
-rw-r--r--core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerPickerActivity.java5
-rw-r--r--core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryStatsViewerActivity.java229
-rw-r--r--core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/TrampolineActivity.java1
-rw-r--r--core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java69
-rw-r--r--core/tests/coretests/src/android/os/OWNERS3
-rw-r--r--core/tests/coretests/src/android/view/ScrollCaptureSearchResultsTest.java121
-rw-r--r--core/tests/coretests/src/android/view/ViewGroupScrollCaptureTest.java61
-rw-r--r--core/tests/coretests/src/android/widget/ProgressBarTest.java21
-rw-r--r--core/tests/coretests/src/android/window/WindowContextTest.java33
-rw-r--r--data/etc/privapp-permissions-platform.xml4
-rw-r--r--errorprone/Android.bp8
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt320
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleFactory.kt8
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt2
-rw-r--r--libs/WindowManager/Shell/res/layout/compat_ui_layout.xml4
-rw-r--r--libs/WindowManager/Shell/res/layout/user_aspect_ratio_settings_layout.xml4
-rw-r--r--libs/WindowManager/Shell/shared/res/values/strings.xml21
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/multiinstance/ManageWindowsViewContainer.kt (renamed from libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/ManageWindowsViewContainer.kt)18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/transition/TransitionStateHolder.kt56
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUILayout.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayout.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxController.kt116
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxData.kt7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxSurfaceBuilder.kt59
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserver.kt62
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/SingleSurfaceLetterboxController.kt136
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt17
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLogger.kt14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt27
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationController.kt553
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationFilter.kt210
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppToWebEducationController.kt52
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppToWebEducationFilter.kt43
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppHandleEducationDatastoreRepository.kt174
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppToWebEducationDatastoreRepository.kt32
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java52
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java23
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java25
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHandleManageWindowsMenu.kt2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenu.kt2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java49
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java22
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java11
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/transition/TransitionStateHolderTest.kt100
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxSurfaceBuilderTest.kt146
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserverTest.kt130
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt42
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt20
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/util/TransitionObserverTestUtils.kt9
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt38
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java13
-rw-r--r--libs/hwui/jni/android_graphics_RenderNode.cpp12
-rw-r--r--media/java/android/media/AudioPlaybackConfiguration.java24
-rw-r--r--media/java/android/media/flags/media_better_together.aconfig8
-rw-r--r--native/android/Android.bp1
-rw-r--r--native/android/display_luts.cpp135
-rw-r--r--native/android/libandroid.map.txt10
-rw-r--r--native/android/surface_control.cpp63
-rw-r--r--native/android/tests/performance_hint/PerformanceHintNativeTest.cpp4
-rw-r--r--nfc/tests/src/android/nfc/NdefMessageTest.java59
-rw-r--r--packages/SettingsLib/IntroPreference/src/com/android/settingslib/widget/IntroPreference.kt1
-rw-r--r--packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java2
-rw-r--r--packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceDataStoreAdapter.kt2
-rw-r--r--packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt22
-rw-r--r--packages/SettingsLib/Preference/src/com/android/settingslib/preference/Utils.kt33
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastCallbackExt.kt9
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java64
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/WritableNamespaces.java1
-rw-r--r--packages/Shell/AndroidManifest.xml3
-rw-r--r--packages/StatementService/Android.bp2
-rw-r--r--packages/StatementService/src/com/android/statementservice/database/Converters.kt130
-rw-r--r--packages/StatementService/src/com/android/statementservice/database/DomainGroups.kt27
-rw-r--r--packages/StatementService/src/com/android/statementservice/database/DomainGroupsDao.kt36
-rw-r--r--packages/StatementService/src/com/android/statementservice/database/DomainGroupsDatabase.kt41
-rw-r--r--packages/StatementService/src/com/android/statementservice/domain/DomainVerificationReceiverV1.kt4
-rw-r--r--packages/StatementService/src/com/android/statementservice/domain/DomainVerifier.kt21
-rw-r--r--packages/StatementService/src/com/android/statementservice/domain/worker/BaseRequestWorker.kt14
-rw-r--r--packages/StatementService/src/com/android/statementservice/domain/worker/GroupUpdateV1Worker.kt55
-rw-r--r--packages/StatementService/src/com/android/statementservice/domain/worker/RetryRequestWorker.kt46
-rw-r--r--packages/StatementService/src/com/android/statementservice/domain/worker/SingleV1RequestWorker.kt10
-rw-r--r--packages/StatementService/src/com/android/statementservice/domain/worker/SingleV2RequestWorker.kt7
-rw-r--r--packages/StatementService/src/com/android/statementservice/network/retriever/StatementParser.kt19
-rw-r--r--packages/StatementService/src/com/android/statementservice/retriever/DynamicAppLinkComponent.java2
-rw-r--r--packages/StatementService/src/com/android/statementservice/retriever/JsonParser.java35
-rw-r--r--packages/SystemUI/Android.bp216
-rw-r--r--packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java7
-rw-r--r--packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java14
-rw-r--r--packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java10
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig10
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt9
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalTouchableSurface.kt5
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt8
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt53
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt10
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefsTest.java17
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/activity/data/repository/ActivityManagerRepositoryTest.kt143
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt12
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/doze/DozeScreenStateTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java)6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModelTest.kt145
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt19
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt98
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt122
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt)114
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLockWhileAwakeInteractorTest.kt68
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorTest.kt125
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java8
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModelTest.kt94
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt28
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java12
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerImplTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryTest.kt89
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadePrimaryDisplayCommandTest.kt59
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/display/AnyExternalShadeDisplayPolicyTest.kt84
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorTest.kt65
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModelTest.kt29
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shared/plugins/PluginActionManagerTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginActionManagerTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java)3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/PulseExpansionHandlerTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt207
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorTest.kt259
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt29
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt9
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorControllerTest.kt16
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt11
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorTest.kt8
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt21
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt49
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/AvalancheControllerTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt)12
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplTest.java (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java)114
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerPhoneTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt)9
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerTestUtil.java (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTestUtil.java)2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/TestableHeadsUpManager.java (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java)14
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt48
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestUtil.kt8
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorTest.kt210
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java5
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java77
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt104
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java5
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt7
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java19
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/MultiDisplayAutoHideControllerStoreTest.kt5
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ShadeTouchableRegionManagerTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManagerTest.kt)4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/data/repository/KeyguardBypassRepositoryTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/data/repository/KeyguardBypassRepositoryTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogEventLoggerTest.kt (renamed from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogEventLoggerTest.kt)0
-rw-r--r--packages/SystemUI/plugin/Android.bp2
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockAnimations.kt61
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockConfig.kt65
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockController.kt44
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockEvents.kt48
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockFaceController.kt47
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockFaceEvents.kt63
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockFaceLayout.kt174
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockMessageBuffers.kt30
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockPickerConfig.kt87
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt548
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockSettings.kt110
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java5
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/TileDetailsViewModel.kt37
-rw-r--r--packages/SystemUI/res/layout/audio_sharing_dialog.xml4
-rw-r--r--packages/SystemUI/res/values/strings.xml2
-rw-r--r--packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java7
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java102
-rw-r--r--packages/SystemUI/src/com/android/keyguard/logging/SimLogger.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefs.java24
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/activity/ActivityManagerModule.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/activity/data/repository/ActivityManagerRepository.kt118
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorImpl.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractor.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/dagger/AudioSharingModule.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModel.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduUiCoordinator.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt105
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutCategoriesUtils.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutCustomizationInteractor.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCategory.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt66
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardLockWhileAwakeInteractor.kt49
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardServiceLockNowInteractor.kt73
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractor.kt61
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java45
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileDetails.kt121
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/ResizingState.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModel.kt65
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTileNewImpl.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailedViewModel.kt59
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractor.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadePrimaryDisplayCommand.kt51
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeSurfaceImpl.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/data/repository/FakeShadeDisplayRepository.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepository.kt47
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/display/AnyExternalShadeDisplayPolicy.kt57
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/display/ShadeDisplayPolicy.kt45
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/display/SpecificDisplayIdPolicy.kt33
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt93
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModel.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt111
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/StatusBarChipsModule.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt115
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractor.kt103
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorController.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinator.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpRowRepository.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/AvalancheController.kt (renamed from packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt)72
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManager.kt (renamed from packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt)18
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerExt.kt (renamed from packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerExt.kt)2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java (renamed from packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java)48
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerLogger.kt (renamed from packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt)52
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpModule.kt (renamed from packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpModule.kt)6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpNotificationViewControllerEmptyImpl.kt (renamed from packages/SystemUI/src/com/android/systemui/statusbar/notification/HeadsUpNotificationViewControllerEmptyImpl.kt)4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpTouchHelper.java (renamed from packages/SystemUI/src/com/android/systemui/statusbar/notification/HeadsUpTouchHelper.java)3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpUtil.java (renamed from packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpUtil.java)2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/OnHeadsUpChangedListener.java (renamed from packages/SystemUI/src/com/android/systemui/statusbar/policy/OnHeadsUpChangedListener.java)2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/PinnedStatus.kt39
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/StatusBarHeadsUpChangeListener.java (renamed from packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java)9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt157
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLogger.kt71
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsProvider.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java96
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java100
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java24
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt31
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java36
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationAddXOnHoverToDismiss.kt41
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarControllerImpl.java47
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeTouchableRegionManager.java (renamed from packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java)10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt473
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java540
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java52
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java)43
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/animation/back/FlingOnBackAnimationCallbackTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/animation/back/FlingOnBackAnimationCallbackTest.kt)0
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt)1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorTest.kt15
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt)0
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/education/domain/ui/view/ContextualEduDialogTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduDialogTest.kt)0
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt)0
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java91
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt53
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt16
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt)0
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModelParameterizedTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModelParameterizedTest.kt)0
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt)0
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java74
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/MotionEventHelper.java (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/MotionEventHelper.java)0
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/TestableWindowManager.java (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/TestableWindowManager.java)0
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/activity/data/repository/ActivityManagerRepositoryKosmos.kt51
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/doze/DozeServiceFake.java (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/doze/DozeServiceFake.java)0
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt7
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt)0
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractorKosmos.kt8
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLockWhileAwakeInteractorKosmos.kt1
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardServiceLockNowInteractor.kt23
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSTile.kt9
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeTileDetailsViewModel.kt48
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModelKosmos.kt (renamed from ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodConfigState.java)12
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapterKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryKosmos.kt34
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorFactoryKosmos.kt33
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorKosmos.kt12
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelKosmos.kt7
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/FakeHeadsUpRowRepository.kt20
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerKosmos.kt (renamed from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/HeadsUpManagerKosmos.kt)2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/FakePromotedNotificationsProvider.kt27
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorKosmos.kt29
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLoggerKosmos.kt23
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsProviderKosmos.kt3
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt19
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/OnUserInteractionCallbackKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmKosmos.kt6
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/DozeServiceHostKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ShadeTouchableRegionManagerKosmos.kt (renamed from packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManagerKosmos.kt)7
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt2
-rw-r--r--packages/Vcn/service-b/Android.bp13
-rw-r--r--packages/Vcn/service-b/vcn-location-flag/module/com/android/server/vcn/VcnLocation.java31
-rw-r--r--packages/Vcn/service-b/vcn-location-flag/platform/com/android/server/vcn/VcnLocation.java32
-rw-r--r--ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java3
-rw-r--r--ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodConfigState.java85
-rw-r--r--ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunnerState.java238
-rw-r--r--ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java277
-rw-r--r--ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java (renamed from ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java)84
-rw-r--r--ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java17
-rw-r--r--ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java62
-rw-r--r--ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java38
-rw-r--r--ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodTestProperties.java59
-rw-r--r--ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java6
-rw-r--r--ravenwood/runtime-jni/jni_helper.h1
-rw-r--r--ravenwood/runtime-jni/ravenwood_sysprop.cpp29
-rw-r--r--ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodMultipleRuleTest.java57
-rw-r--r--ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRuleValidationTest.java103
-rw-r--r--ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerConfigValidationTest.java484
-rw-r--r--ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/IdentityTest.java13
-rw-r--r--ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/SystemPropertyTest.java88
-rw-r--r--ravenwood/tests/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesTest.java8
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java29
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java14
-rw-r--r--services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java5
-rw-r--r--services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java7
-rw-r--r--services/backup/java/com/android/server/backup/BackupAgentConnectionManager.java148
-rw-r--r--services/core/java/com/android/server/BatteryService.java54
-rw-r--r--services/core/java/com/android/server/BinaryTransparencyService.java10
-rw-r--r--services/core/java/com/android/server/TEST_MAPPING4
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerShellCommand.java9
-rw-r--r--services/core/java/com/android/server/am/BatteryStatsService.java39
-rw-r--r--services/core/java/com/android/server/am/OWNERS3
-rw-r--r--services/core/java/com/android/server/am/SettingsToPropertiesMapper.java8
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java27
-rw-r--r--services/core/java/com/android/server/audio/PlaybackActivityMonitor.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.aconfig11
-rw-r--r--services/core/java/com/android/server/hdmi/Constants.java7
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiControlService.java13
-rw-r--r--services/core/java/com/android/server/location/contexthub/ContextHubService.java26
-rw-r--r--services/core/java/com/android/server/pm/InstallDependencyHelper.java19
-rw-r--r--services/core/java/com/android/server/pm/PackageAbiHelper.java20
-rw-r--r--services/core/java/com/android/server/pm/PackageAbiHelperImpl.java16
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java75
-rw-r--r--services/core/java/com/android/server/pm/PackageSetting.java91
-rw-r--r--services/core/java/com/android/server/pm/SaferIntentUtils.java122
-rw-r--r--services/core/java/com/android/server/pm/ScanPackageUtils.java32
-rw-r--r--services/core/java/com/android/server/pm/Settings.java15
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java7
-rw-r--r--services/core/java/com/android/server/pm/pkg/PackageState.java16
-rw-r--r--services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java10
-rw-r--r--services/core/java/com/android/server/pm/pkg/mutate/PackageStateWrite.java4
-rw-r--r--services/core/java/com/android/server/power/hint/HintManagerService.java281
-rw-r--r--services/core/java/com/android/server/power/hint/flags.aconfig7
-rw-r--r--services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java29
-rw-r--r--services/core/java/com/android/server/power/stats/processor/PowerStatsExporter.java16
-rw-r--r--services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java8
-rw-r--r--services/core/java/com/android/server/security/advancedprotection/features/DisallowCellular2GAdvancedProtectionHook.java86
-rw-r--r--services/core/java/com/android/server/security/advancedprotection/features/MemoryTaggingExtensionHook.java81
-rw-r--r--services/core/java/com/android/server/security/intrusiondetection/DataAggregator.java14
-rw-r--r--services/core/java/com/android/server/security/intrusiondetection/DataSource.java5
-rw-r--r--services/core/java/com/android/server/security/intrusiondetection/IntrusionDetectionAdminReceiver.java42
-rw-r--r--services/core/java/com/android/server/security/intrusiondetection/NetworkLogSource.java134
-rw-r--r--services/core/java/com/android/server/security/intrusiondetection/SecurityLogSource.java28
-rw-r--r--services/core/java/com/android/server/trust/TrustManagerService.java30
-rw-r--r--services/core/java/com/android/server/utils/WatchableImpl.java5
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperManagerService.java2
-rw-r--r--services/core/java/com/android/server/wm/AccessibilityController.java515
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java4
-rw-r--r--services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java6
-rw-r--r--services/core/java/com/android/server/wm/AppCompatCameraOverrides.java8
-rw-r--r--services/core/java/com/android/server/wm/AppCompatConfiguration.java29
-rw-r--r--services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java5
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerShellCommand.java21
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java99
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/Owners.java13
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java11
-rw-r--r--services/java/com/android/server/SystemServer.java9
-rw-r--r--services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java12
-rw-r--r--services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt1
-rw-r--r--services/tests/mockingservicestests/Android.bp7
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/BatteryServiceTest.java87
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java19
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/backup/BackupAgentConnectionManagerTest.java26
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java71
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java23
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java68
-rw-r--r--services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java159
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/AmbientDisplayPowerCalculatorTest.java8
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsAtomTest.java61
-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/BatteryUsageStatsTest.java74
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/BluetoothPowerCalculatorTest.java40
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/CameraPowerCalculatorTest.java16
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerCalculatorTest.java16
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/GnssPowerCalculatorTest.java14
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java50
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/ScreenPowerCalculatorTest.java36
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerCalculatorTest.java20
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/processor/BasePowerStatsProcessorTest.java1
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/processor/PowerStatsExporterTest.java8
-rw-r--r--services/tests/security/intrusiondetection/AndroidManifest.xml13
-rw-r--r--services/tests/security/intrusiondetection/res/xml/device_admin.xml18
-rw-r--r--services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/IntrusionDetectionServiceTest.java224
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java22
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java33
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java17
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java3
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java52
-rw-r--r--telephony/java/android/telephony/satellite/ISelectedNbIotSatelliteSubscriptionCallback.aidl31
-rw-r--r--telephony/java/android/telephony/satellite/SatelliteInfo.java14
-rw-r--r--telephony/java/android/telephony/satellite/SatelliteManager.java93
-rw-r--r--telephony/java/android/telephony/satellite/SelectedNbIotSatelliteSubscriptionCallback.java31
-rw-r--r--telephony/java/android/telephony/satellite/SystemSelectionSpecifier.java104
-rw-r--r--telephony/java/android/telephony/satellite/stub/EarfcnRange.aidl32
-rw-r--r--telephony/java/android/telephony/satellite/stub/SatelliteInfo.aidl52
-rw-r--r--telephony/java/android/telephony/satellite/stub/SatellitePosition.aidl32
-rw-r--r--telephony/java/android/telephony/satellite/stub/SystemSelectionSpecifier.aidl15
-rw-r--r--telephony/java/android/telephony/satellite/stub/UUID.aidl32
-rw-r--r--telephony/java/com/android/internal/telephony/ITelephony.aidl25
-rw-r--r--tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java5
-rw-r--r--tools/systemfeatures/Android.bp8
-rw-r--r--tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesMetadataProcessor.kt110
-rw-r--r--tools/systemfeatures/tests/src/PackageManager.java19
-rw-r--r--tools/systemfeatures/tests/src/SystemFeaturesMetadataProcessorTest.java36
595 files changed, 16241 insertions, 6688 deletions
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index a92a54337f96..d748a3bebfef 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -18,7 +18,7 @@ clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp
tests/
tools/
bpfmt = -d
-ktfmt = --kotlinlang-style --include-dirs=services/permission,packages/SystemUI,libs/WindowManager/Shell/src/com/android/wm/shell/freeform
+ktfmt = --kotlinlang-style --include-dirs=services/permission,packages/SystemUI,libs/WindowManager/Shell/src/com/android/wm/shell/freeform,libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education
[Hook Scripts]
checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT}
diff --git a/apex/jobscheduler/service/aconfig/app_idle.aconfig b/apex/jobscheduler/service/aconfig/app_idle.aconfig
index f079c02707e0..74d2a590086f 100644
--- a/apex/jobscheduler/service/aconfig/app_idle.aconfig
+++ b/apex/jobscheduler/service/aconfig/app_idle.aconfig
@@ -21,3 +21,10 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "adjust_default_bucket_elevation_params"
+ namespace: "backstage_power"
+ description: "Adjust the default bucket evaluation parameters"
+ bug: "379909479"
+}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 963307b110cf..a5a08fb9997c 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -573,6 +573,7 @@ public class JobSchedulerService extends com.android.server.SystemService
case Constants.KEY_MIN_LINEAR_BACKOFF_TIME_MS:
case Constants.KEY_MIN_EXP_BACKOFF_TIME_MS:
case Constants.KEY_SYSTEM_STOP_TO_FAILURE_RATIO:
+ case Constants.KEY_ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF:
mConstants.updateBackoffConstantsLocked();
break;
case Constants.KEY_CONN_CONGESTION_DELAY_FRAC:
@@ -679,6 +680,8 @@ public class JobSchedulerService extends com.android.server.SystemService
private static final String KEY_MIN_EXP_BACKOFF_TIME_MS = "min_exp_backoff_time_ms";
private static final String KEY_SYSTEM_STOP_TO_FAILURE_RATIO =
"system_stop_to_failure_ratio";
+ private static final String KEY_ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF =
+ "abandoned_job_timeouts_before_aggressive_backoff";
private static final String KEY_CONN_CONGESTION_DELAY_FRAC = "conn_congestion_delay_frac";
private static final String KEY_CONN_PREFETCH_RELAX_FRAC = "conn_prefetch_relax_frac";
private static final String KEY_CONN_USE_CELL_SIGNAL_STRENGTH =
@@ -750,6 +753,7 @@ public class JobSchedulerService extends com.android.server.SystemService
private static final long DEFAULT_MIN_LINEAR_BACKOFF_TIME_MS = JobInfo.MIN_BACKOFF_MILLIS;
private static final long DEFAULT_MIN_EXP_BACKOFF_TIME_MS = JobInfo.MIN_BACKOFF_MILLIS;
private static final int DEFAULT_SYSTEM_STOP_TO_FAILURE_RATIO = 3;
+ private static final int DEFAULT_ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF = 3;
private static final float DEFAULT_CONN_CONGESTION_DELAY_FRAC = 0.5f;
private static final float DEFAULT_CONN_PREFETCH_RELAX_FRAC = 0.5f;
private static final boolean DEFAULT_CONN_USE_CELL_SIGNAL_STRENGTH = true;
@@ -845,7 +849,12 @@ public class JobSchedulerService extends com.android.server.SystemService
* incremental failure in the backoff policy calculation.
*/
int SYSTEM_STOP_TO_FAILURE_RATIO = DEFAULT_SYSTEM_STOP_TO_FAILURE_RATIO;
-
+ /**
+ * Number of consecutive timeouts by abandoned jobs before we change to aggressive backoff
+ * policy.
+ */
+ int ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF =
+ DEFAULT_ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF;
/**
* The fraction of a job's running window that must pass before we
* consider running it when the network is congested.
@@ -1078,6 +1087,10 @@ public class JobSchedulerService extends com.android.server.SystemService
SYSTEM_STOP_TO_FAILURE_RATIO = DeviceConfig.getInt(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
KEY_SYSTEM_STOP_TO_FAILURE_RATIO,
DEFAULT_SYSTEM_STOP_TO_FAILURE_RATIO);
+ ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF = DeviceConfig.getInt(
+ DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+ KEY_ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF,
+ DEFAULT_ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF);
}
// TODO(141645789): move into ConnectivityController.CcConfig
@@ -1287,6 +1300,8 @@ public class JobSchedulerService extends com.android.server.SystemService
pw.print(KEY_MIN_LINEAR_BACKOFF_TIME_MS, MIN_LINEAR_BACKOFF_TIME_MS).println();
pw.print(KEY_MIN_EXP_BACKOFF_TIME_MS, MIN_EXP_BACKOFF_TIME_MS).println();
pw.print(KEY_SYSTEM_STOP_TO_FAILURE_RATIO, SYSTEM_STOP_TO_FAILURE_RATIO).println();
+ pw.print(KEY_ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF,
+ ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF).println();
pw.print(KEY_CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC).println();
pw.print(KEY_CONN_PREFETCH_RELAX_FRAC, CONN_PREFETCH_RELAX_FRAC).println();
pw.print(KEY_CONN_USE_CELL_SIGNAL_STRENGTH, CONN_USE_CELL_SIGNAL_STRENGTH).println();
@@ -2997,6 +3012,7 @@ public class JobSchedulerService extends com.android.server.SystemService
final long initialBackoffMillis = job.getInitialBackoffMillis();
int numFailures = failureToReschedule.getNumFailures();
+ int numAbandonedFailures = failureToReschedule.getNumAbandonedFailures();
int numSystemStops = failureToReschedule.getNumSystemStops();
// We should back off slowly if JobScheduler keeps stopping the job,
// but back off immediately if the issue appeared to be the app's fault
@@ -3006,9 +3022,19 @@ public class JobSchedulerService extends com.android.server.SystemService
|| internalStopReason == JobParameters.INTERNAL_STOP_REASON_ANR
|| stopReason == JobParameters.STOP_REASON_USER) {
numFailures++;
+ } else if (android.app.job.Flags.handleAbandonedJobs()
+ && internalStopReason == JobParameters.INTERNAL_STOP_REASON_TIMEOUT_ABANDONED) {
+ numAbandonedFailures++;
+ numFailures++;
} else {
numSystemStops++;
}
+
+ int backoffPolicy = job.getBackoffPolicy();
+ if (shouldUseAggressiveBackoff(numAbandonedFailures)) {
+ backoffPolicy = JobInfo.BACKOFF_POLICY_EXPONENTIAL;
+ }
+
final int backoffAttempts =
numFailures + numSystemStops / mConstants.SYSTEM_STOP_TO_FAILURE_RATIO;
final long earliestRuntimeMs;
@@ -3017,7 +3043,7 @@ public class JobSchedulerService extends com.android.server.SystemService
earliestRuntimeMs = JobStatus.NO_EARLIEST_RUNTIME;
} else {
long delayMillis;
- switch (job.getBackoffPolicy()) {
+ switch (backoffPolicy) {
case JobInfo.BACKOFF_POLICY_LINEAR: {
long backoff = initialBackoffMillis;
if (backoff < mConstants.MIN_LINEAR_BACKOFF_TIME_MS) {
@@ -3046,7 +3072,7 @@ public class JobSchedulerService extends com.android.server.SystemService
}
JobStatus newJob = new JobStatus(failureToReschedule,
earliestRuntimeMs,
- JobStatus.NO_LATEST_RUNTIME, numFailures, numSystemStops,
+ JobStatus.NO_LATEST_RUNTIME, numFailures, numAbandonedFailures, numSystemStops,
failureToReschedule.getLastSuccessfulRunTime(), sSystemClock.millis(),
failureToReschedule.getCumulativeExecutionTimeMs());
if (stopReason == JobParameters.STOP_REASON_USER) {
@@ -3069,6 +3095,20 @@ public class JobSchedulerService extends com.android.server.SystemService
}
/**
+ * Returns {@code true} if the given number of abandoned failures indicates that JobScheduler
+ * should use an aggressive backoff policy.
+ *
+ * @param numAbandonedFailures The number of abandoned failures.
+ * @return {@code true} if the given number of abandoned failures indicates that JobScheduler
+ * should use an aggressive backoff policy.
+ */
+ public boolean shouldUseAggressiveBackoff(int numAbandonedFailures) {
+ return android.app.job.Flags.handleAbandonedJobs()
+ && numAbandonedFailures
+ > mConstants.ABANDONED_JOB_TIMEOUTS_BEFORE_AGGRESSIVE_BACKOFF;
+ }
+
+ /**
* Maximum time buffer in which JobScheduler will try to optimize periodic job scheduling. This
* does not cause a job's period to be larger than requested (eg: if the requested period is
* shorter than this buffer). This is used to put a limit on when JobScheduler will intervene
@@ -3147,6 +3187,7 @@ public class JobSchedulerService extends com.android.server.SystemService
return new JobStatus(periodicToReschedule,
elapsedNow + period - flex, elapsedNow + period,
0 /* numFailures */, 0 /* numSystemStops */,
+ 0 /* numAbandonedFailures */,
sSystemClock.millis() /* lastSuccessfulRunTime */,
periodicToReschedule.getLastFailedRunTime(),
0 /* Reset cumulativeExecutionTime because of successful execution */);
@@ -3163,6 +3204,7 @@ public class JobSchedulerService extends com.android.server.SystemService
return new JobStatus(periodicToReschedule,
newEarliestRunTimeElapsed, newLatestRuntimeElapsed,
0 /* numFailures */, 0 /* numSystemStops */,
+ 0 /* numAbandonedFailures */,
sSystemClock.millis() /* lastSuccessfulRunTime */,
periodicToReschedule.getLastFailedRunTime(),
0 /* Reset cumulativeExecutionTime because of successful execution */);
@@ -3171,6 +3213,10 @@ public class JobSchedulerService extends com.android.server.SystemService
@VisibleForTesting
void maybeProcessBuggyJob(@NonNull JobStatus jobStatus, int debugStopReason) {
boolean jobTimedOut = debugStopReason == JobParameters.INTERNAL_STOP_REASON_TIMEOUT;
+ if (android.app.job.Flags.handleAbandonedJobs()) {
+ jobTimedOut |= (debugStopReason
+ == JobParameters.INTERNAL_STOP_REASON_TIMEOUT_ABANDONED);
+ }
// If madeActive = 0, the job never actually started.
if (!jobTimedOut && jobStatus.madeActive > 0) {
final long executionDurationMs = sUptimeMillisClock.millis() - jobStatus.madeActive;
@@ -3252,9 +3298,12 @@ public class JobSchedulerService extends com.android.server.SystemService
// we stop it.
final JobStatus rescheduledJob = needsReschedule
? getRescheduleJobForFailureLocked(jobStatus, stopReason, debugStopReason) : null;
+ final boolean isStopReasonAbandoned = android.app.job.Flags.handleAbandonedJobs()
+ && (debugStopReason == JobParameters.INTERNAL_STOP_REASON_TIMEOUT_ABANDONED);
if (rescheduledJob != null
&& !rescheduledJob.shouldTreatAsUserInitiatedJob()
&& (debugStopReason == JobParameters.INTERNAL_STOP_REASON_TIMEOUT
+ || isStopReasonAbandoned
|| debugStopReason == JobParameters.INTERNAL_STOP_REASON_PREEMPT)) {
rescheduledJob.disallowRunInBatterySaverAndDoze();
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
index d8934d8f83b8..dfb36818c818 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
@@ -269,7 +269,9 @@ public final class JobStore {
convertRtcBoundsToElapsed(utcTimes, elapsedNow);
JobStatus newJob = new JobStatus(job,
elapsedRuntimes.first, elapsedRuntimes.second,
- 0, 0, job.getLastSuccessfulRunTime(), job.getLastFailedRunTime(),
+ 0 /* numFailures */, 0 /* numAbandonedFailures */,
+ 0 /* numSystemStops */, job.getLastSuccessfulRunTime(),
+ job.getLastFailedRunTime(),
job.getCumulativeExecutionTimeMs());
newJob.prepareLocked();
toAdd.add(newJob);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index a3eaefd5f057..5a33aa0ab7eb 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -316,6 +316,12 @@ public final class JobStatus {
private final int numFailures;
/**
+ * How many times this job has stopped due to {@link
+ * JobParameters#STOP_REASON_TIMEOUT_ABANDONED}.
+ */
+ private final int mNumAbandonedFailures;
+
+ /**
* The number of times JobScheduler has forced this job to stop due to reasons mostly outside
* of the app's control.
*/
@@ -605,6 +611,8 @@ public final class JobStatus {
* @param tag A string associated with the job for debugging/logging purposes.
* @param numFailures Count of how many times this job has requested a reschedule because
* its work was not yet finished.
+ * @param mNumAbandonedFailures Count of how many times this job has requested a reschedule
+ * because it was abandoned.
* @param numSystemStops Count of how many times JobScheduler has forced this job to stop due to
* factors mostly out of the app's control.
* @param earliestRunTimeElapsedMillis Milestone: earliest point in time at which the job
@@ -617,7 +625,7 @@ public final class JobStatus {
*/
private JobStatus(JobInfo job, int callingUid, String sourcePackageName,
int sourceUserId, int standbyBucket, @Nullable String namespace, String tag,
- int numFailures, int numSystemStops,
+ int numFailures, int mNumAbandonedFailures, int numSystemStops,
long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis,
long lastSuccessfulRunTime, long lastFailedRunTime, long cumulativeExecutionTimeMs,
int internalFlags,
@@ -677,6 +685,7 @@ public final class JobStatus {
this.latestRunTimeElapsedMillis = latestRunTimeElapsedMillis;
this.mOriginalLatestRunTimeElapsedMillis = latestRunTimeElapsedMillis;
this.numFailures = numFailures;
+ this.mNumAbandonedFailures = mNumAbandonedFailures;
mNumSystemStops = numSystemStops;
int requiredConstraints = job.getConstraintFlags();
@@ -750,7 +759,8 @@ public final class JobStatus {
this(jobStatus.getJob(), jobStatus.getUid(),
jobStatus.getSourcePackageName(), jobStatus.getSourceUserId(),
jobStatus.getStandbyBucket(), jobStatus.getNamespace(),
- jobStatus.getSourceTag(), jobStatus.getNumFailures(), jobStatus.getNumSystemStops(),
+ jobStatus.getSourceTag(), jobStatus.getNumFailures(),
+ jobStatus.getNumAbandonedFailures(), jobStatus.getNumSystemStops(),
jobStatus.getEarliestRunTime(), jobStatus.getLatestRunTimeElapsed(),
jobStatus.getLastSuccessfulRunTime(), jobStatus.getLastFailedRunTime(),
jobStatus.getCumulativeExecutionTimeMs(),
@@ -787,6 +797,7 @@ public final class JobStatus {
this(job, callingUid, sourcePkgName, sourceUserId,
standbyBucket, namespace,
sourceTag, /* numFailures */ 0, /* numSystemStops */ 0,
+ /* mNumAbandonedFailures */ 0,
earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis,
lastSuccessfulRunTime, lastFailedRunTime, cumulativeExecutionTimeMs,
innerFlags, dynamicConstraints);
@@ -806,13 +817,15 @@ public final class JobStatus {
/** Create a new job to be rescheduled with the provided parameters. */
public JobStatus(JobStatus rescheduling,
long newEarliestRuntimeElapsedMillis,
- long newLatestRuntimeElapsedMillis, int numFailures, int numSystemStops,
+ long newLatestRuntimeElapsedMillis, int numFailures,
+ int mNumAbandonedFailures, int numSystemStops,
long lastSuccessfulRunTime, long lastFailedRunTime,
long cumulativeExecutionTimeMs) {
this(rescheduling.job, rescheduling.getUid(),
rescheduling.getSourcePackageName(), rescheduling.getSourceUserId(),
rescheduling.getStandbyBucket(), rescheduling.getNamespace(),
- rescheduling.getSourceTag(), numFailures, numSystemStops,
+ rescheduling.getSourceTag(), numFailures,
+ mNumAbandonedFailures, numSystemStops,
newEarliestRuntimeElapsedMillis,
newLatestRuntimeElapsedMillis,
lastSuccessfulRunTime, lastFailedRunTime, cumulativeExecutionTimeMs,
@@ -851,7 +864,8 @@ public final class JobStatus {
int standbyBucket = JobSchedulerService.standbyBucketForPackage(jobPackage,
sourceUserId, elapsedNow);
return new JobStatus(job, callingUid, sourcePkg, sourceUserId,
- standbyBucket, namespace, tag, /* numFailures */ 0, /* numSystemStops */ 0,
+ standbyBucket, namespace, tag, /* numFailures */ 0,
+ /* mNumAbandonedFailures */ 0, /* numSystemStops */ 0,
earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis,
0 /* lastSuccessfulRunTime */, 0 /* lastFailedRunTime */,
/* cumulativeExecutionTime */ 0,
@@ -1146,6 +1160,13 @@ public final class JobStatus {
}
/**
+ * Returns the number of times the job stopped previously for STOP_REASON_TIMEOUT_ABANDONED.
+ */
+ public int getNumAbandonedFailures() {
+ return mNumAbandonedFailures;
+ }
+
+ /**
* Returns the number of times the system stopped a previous execution of this job for reasons
* that were likely outside the app's control.
*/
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index c9d340757c6b..9871d713178e 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -337,11 +337,11 @@ public class AppStandbyController
*/
long[] mAppStandbyElapsedThresholds = DEFAULT_ELAPSED_TIME_THRESHOLDS;
/** Minimum time a strong usage event should keep the bucket elevated. */
- long mStrongUsageTimeoutMillis = ConstantsObserver.DEFAULT_STRONG_USAGE_TIMEOUT;
+ long mStrongUsageTimeoutMillis = ConstantsObserver.DEFAULT_LEGACY_STRONG_USAGE_TIMEOUT;
/** Minimum time a notification seen event should keep the bucket elevated. */
long mNotificationSeenTimeoutMillis = ConstantsObserver.DEFAULT_NOTIFICATION_TIMEOUT;
/** Minimum time a slice pinned event should keep the bucket elevated. */
- long mSlicePinnedTimeoutMillis = ConstantsObserver.DEFAULT_SLICE_PINNED_TIMEOUT;
+ long mSlicePinnedTimeoutMillis = ConstantsObserver.DEFAULT_LEGACY_SLICE_PINNED_TIMEOUT;
/** The standby bucket that an app will be promoted on a notification-seen event */
int mNotificationSeenPromotedBucket =
ConstantsObserver.DEFAULT_NOTIFICATION_SEEN_PROMOTED_BUCKET;
@@ -362,7 +362,9 @@ public class AppStandbyController
/** Maximum time to wait for a prediction before using simple timeouts to downgrade buckets. */
long mPredictionTimeoutMillis = DEFAULT_PREDICTION_TIMEOUT;
/** Maximum time a sync adapter associated with a CP should keep the buckets elevated. */
- long mSyncAdapterTimeoutMillis = ConstantsObserver.DEFAULT_SYNC_ADAPTER_TIMEOUT;
+ long mSyncAdapterTimeoutMillis = ConstantsObserver.DEFAULT_LEGACY_SYNC_ADAPTER_TIMEOUT;
+ /** The bucket that an app will be promoted on a sync adapter associated with a CP */
+ int mSyncAdapaterPromotedBucket = STANDBY_BUCKET_ACTIVE;
/**
* Maximum time an exempted sync should keep the buckets elevated, when sync is scheduled in
* non-doze
@@ -751,7 +753,7 @@ public class AppStandbyController
userId);
synchronized (mAppIdleLock) {
reportNoninteractiveUsageCrossUserLocked(packageName, userId,
- STANDBY_BUCKET_ACTIVE, REASON_SUB_USAGE_SYNC_ADAPTER,
+ mSyncAdapaterPromotedBucket, REASON_SUB_USAGE_SYNC_ADAPTER,
elapsedRealtime, mSyncAdapterTimeoutMillis, linkedProfiles);
}
}
@@ -2446,6 +2448,8 @@ public class AppStandbyController
pw.println("Flags: ");
pw.println(" " + Flags.FLAG_AVOID_IDLE_CHECK
+ ": " + Flags.avoidIdleCheck());
+ pw.println(" " + Flags.FLAG_ADJUST_DEFAULT_BUCKET_ELEVATION_PARAMS
+ + ": " + Flags.adjustDefaultBucketElevationParams());
pw.println();
synchronized (mCarrierPrivilegedLock) {
@@ -2481,6 +2485,9 @@ public class AppStandbyController
pw.print(" mSyncAdapterTimeoutMillis=");
TimeUtils.formatDuration(mSyncAdapterTimeoutMillis, pw);
pw.println();
+ pw.print(" mSyncAdapaterPromotedBucket=");
+ pw.print(standbyBucketToString(mSyncAdapaterPromotedBucket));
+ pw.println();
pw.print(" mSystemInteractionTimeoutMillis=");
TimeUtils.formatDuration(mSystemInteractionTimeoutMillis, pw);
pw.println();
@@ -3059,12 +3066,18 @@ public class AppStandbyController
public static final long DEFAULT_CHECK_IDLE_INTERVAL_MS =
COMPRESS_TIME ? ONE_MINUTE : 4 * ONE_HOUR;
- public static final long DEFAULT_STRONG_USAGE_TIMEOUT =
+ public static final long DEFAULT_LEGACY_STRONG_USAGE_TIMEOUT =
COMPRESS_TIME ? ONE_MINUTE : 1 * ONE_HOUR;
+
+ public static final long DEFAULT_CURRENT_STRONG_USAGE_TIMEOUT =
+ COMPRESS_TIME ? ONE_MINUTE : 5 * ONE_MINUTE;
public static final long DEFAULT_NOTIFICATION_TIMEOUT =
COMPRESS_TIME ? 12 * ONE_MINUTE : 12 * ONE_HOUR;
- public static final long DEFAULT_SLICE_PINNED_TIMEOUT =
+ public static final long DEFAULT_LEGACY_SLICE_PINNED_TIMEOUT =
COMPRESS_TIME ? 12 * ONE_MINUTE : 12 * ONE_HOUR;
+
+ public static final long DEFAULT_CURRENT_SLICE_PINNED_TIMEOUT =
+ COMPRESS_TIME ? 12 * ONE_MINUTE : 2 * ONE_HOUR;
public static final int DEFAULT_NOTIFICATION_SEEN_PROMOTED_BUCKET =
STANDBY_BUCKET_WORKING_SET;
public static final boolean DEFAULT_RETAIN_NOTIFICATION_SEEN_IMPACT_FOR_PRE_T_APPS = false;
@@ -3073,8 +3086,11 @@ public class AppStandbyController
COMPRESS_TIME ? 2 * ONE_MINUTE : 2 * ONE_HOUR;
public static final long DEFAULT_SYSTEM_INTERACTION_TIMEOUT =
COMPRESS_TIME ? ONE_MINUTE : 10 * ONE_MINUTE;
- public static final long DEFAULT_SYNC_ADAPTER_TIMEOUT =
+ public static final long DEFAULT_LEGACY_SYNC_ADAPTER_TIMEOUT =
COMPRESS_TIME ? ONE_MINUTE : 10 * ONE_MINUTE;
+
+ public static final long DEFAULT_CURRENT_SYNC_ADAPTER_TIMEOUT =
+ COMPRESS_TIME ? ONE_MINUTE : 2 * ONE_HOUR;
public static final long DEFAULT_EXEMPTED_SYNC_SCHEDULED_NON_DOZE_TIMEOUT =
COMPRESS_TIME ? (ONE_MINUTE / 2) : 10 * ONE_MINUTE;
public static final long DEFAULT_EXEMPTED_SYNC_SCHEDULED_DOZE_TIMEOUT =
@@ -3117,6 +3133,9 @@ public class AppStandbyController
cr.registerContentObserver(Global.getUriFor(Global.ADAPTIVE_BATTERY_MANAGEMENT_ENABLED),
false, this);
mInjector.registerDeviceConfigPropertiesChangedListener(this);
+
+ processDefaultConstants();
+
// Load all the constants.
// postOneTimeCheckIdleStates() doesn't need to be called on boot.
processProperties(mInjector.getDeviceConfigProperties());
@@ -3135,6 +3154,17 @@ public class AppStandbyController
postOneTimeCheckIdleStates();
}
+ private void processDefaultConstants() {
+ if (!Flags.adjustDefaultBucketElevationParams()) {
+ return;
+ }
+
+ mSlicePinnedTimeoutMillis = DEFAULT_CURRENT_SLICE_PINNED_TIMEOUT;
+ mSyncAdapterTimeoutMillis = DEFAULT_CURRENT_SYNC_ADAPTER_TIMEOUT;
+ mSyncAdapaterPromotedBucket = STANDBY_BUCKET_WORKING_SET;
+ mStrongUsageTimeoutMillis = DEFAULT_CURRENT_STRONG_USAGE_TIMEOUT;
+ }
+
private void processProperties(DeviceConfig.Properties properties) {
boolean timeThresholdsUpdated = false;
synchronized (mAppIdleLock) {
@@ -3182,11 +3212,16 @@ public class AppStandbyController
case KEY_SLICE_PINNED_HOLD_DURATION:
mSlicePinnedTimeoutMillis = properties.getLong(
KEY_SLICE_PINNED_HOLD_DURATION,
- DEFAULT_SLICE_PINNED_TIMEOUT);
+ Flags.adjustDefaultBucketElevationParams()
+ ? DEFAULT_CURRENT_SLICE_PINNED_TIMEOUT
+ : DEFAULT_LEGACY_SLICE_PINNED_TIMEOUT);
break;
case KEY_STRONG_USAGE_HOLD_DURATION:
mStrongUsageTimeoutMillis = properties.getLong(
- KEY_STRONG_USAGE_HOLD_DURATION, DEFAULT_STRONG_USAGE_TIMEOUT);
+ KEY_STRONG_USAGE_HOLD_DURATION,
+ Flags.adjustDefaultBucketElevationParams()
+ ? DEFAULT_CURRENT_STRONG_USAGE_TIMEOUT
+ : DEFAULT_LEGACY_STRONG_USAGE_TIMEOUT);
break;
case KEY_PREDICTION_TIMEOUT:
mPredictionTimeoutMillis = properties.getLong(
@@ -3203,7 +3238,10 @@ public class AppStandbyController
break;
case KEY_SYNC_ADAPTER_HOLD_DURATION:
mSyncAdapterTimeoutMillis = properties.getLong(
- KEY_SYNC_ADAPTER_HOLD_DURATION, DEFAULT_SYNC_ADAPTER_TIMEOUT);
+ KEY_SYNC_ADAPTER_HOLD_DURATION,
+ Flags.adjustDefaultBucketElevationParams()
+ ? DEFAULT_CURRENT_SYNC_ADAPTER_TIMEOUT
+ : DEFAULT_LEGACY_SYNC_ADAPTER_TIMEOUT);
break;
case KEY_EXEMPTED_SYNC_SCHEDULED_DOZE_HOLD_DURATION:
mExemptedSyncScheduledDozeTimeoutMillis = properties.getLong(
diff --git a/core/api/current.txt b/core/api/current.txt
index 3af8e7e17dbd..af7d6f1a5da1 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -13225,8 +13225,8 @@ package android.content.pm {
public abstract class PackageManager {
ctor @Deprecated public PackageManager();
method @Deprecated public abstract void addPackageToPreferred(@NonNull String);
- method public abstract boolean addPermission(@NonNull android.content.pm.PermissionInfo);
- method public abstract boolean addPermissionAsync(@NonNull android.content.pm.PermissionInfo);
+ method @Deprecated @FlaggedApi("android.permission.flags.permission_tree_apis_deprecated") public abstract boolean addPermission(@NonNull android.content.pm.PermissionInfo);
+ method @Deprecated @FlaggedApi("android.permission.flags.permission_tree_apis_deprecated") public abstract boolean addPermissionAsync(@NonNull android.content.pm.PermissionInfo);
method @Deprecated public abstract void addPreferredActivity(@NonNull android.content.IntentFilter, int, @Nullable android.content.ComponentName[], @NonNull android.content.ComponentName);
method @RequiresPermission(value="android.permission.WHITELIST_RESTRICTED_PERMISSIONS", conditional=true) public boolean addWhitelistedRestrictedPermission(@NonNull String, @NonNull String, int);
method public boolean canPackageQuery(@NonNull String, @NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -13367,7 +13367,7 @@ package android.content.pm {
method @NonNull public java.util.List<android.content.pm.PackageManager.Property> queryServiceProperty(@NonNull String);
method public void relinquishUpdateOwnership(@NonNull String);
method @Deprecated public abstract void removePackageFromPreferred(@NonNull String);
- method public abstract void removePermission(@NonNull String);
+ method @Deprecated @FlaggedApi("android.permission.flags.permission_tree_apis_deprecated") public abstract void removePermission(@NonNull String);
method @RequiresPermission(value="android.permission.WHITELIST_RESTRICTED_PERMISSIONS", conditional=true) public boolean removeWhitelistedRestrictedPermission(@NonNull String, @NonNull String, int);
method public void requestChecksums(@NonNull String, boolean, int, @NonNull java.util.List<java.security.cert.Certificate>, @NonNull android.content.pm.PackageManager.OnChecksumsReadyListener) throws java.security.cert.CertificateEncodingException, android.content.pm.PackageManager.NameNotFoundException;
method @Nullable public abstract android.content.pm.ResolveInfo resolveActivity(@NonNull android.content.Intent, int);
@@ -33612,9 +33612,14 @@ package android.os {
@FlaggedApi("android.os.cpu_gpu_headrooms") public final class CpuHeadroomParams {
ctor public CpuHeadroomParams();
method public int getCalculationType();
+ method @IntRange(from=android.os.CpuHeadroomParams.CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN, to=android.os.CpuHeadroomParams.CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX) public long getCalculationWindowMillis();
method public void setCalculationType(int);
+ method public void setCalculationWindowMillis(@IntRange(from=android.os.CpuHeadroomParams.CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN, to=android.os.CpuHeadroomParams.CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX) int);
+ method public void setTids(@NonNull int...);
field public static final int CPU_HEADROOM_CALCULATION_TYPE_AVERAGE = 1; // 0x1
field public static final int CPU_HEADROOM_CALCULATION_TYPE_MIN = 0; // 0x0
+ field public static final int CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX = 10000; // 0x2710
+ field public static final int CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN = 50; // 0x32
}
public final class CpuUsageInfo implements android.os.Parcelable {
@@ -33867,9 +33872,13 @@ package android.os {
@FlaggedApi("android.os.cpu_gpu_headrooms") public final class GpuHeadroomParams {
ctor public GpuHeadroomParams();
method public int getCalculationType();
+ method @IntRange(from=android.os.GpuHeadroomParams.GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN, to=android.os.GpuHeadroomParams.GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX) public int getCalculationWindowMillis();
method public void setCalculationType(int);
+ method public void setCalculationWindowMillis(@IntRange(from=android.os.GpuHeadroomParams.GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN, to=android.os.GpuHeadroomParams.GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX) int);
field public static final int GPU_HEADROOM_CALCULATION_TYPE_AVERAGE = 1; // 0x1
field public static final int GPU_HEADROOM_CALCULATION_TYPE_MIN = 0; // 0x0
+ field public static final int GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX = 10000; // 0x2710
+ field public static final int GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN = 50; // 0x32
}
public class Handler {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 5c0b22792348..83699ac30939 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3940,6 +3940,7 @@ package android.content {
field public static final String WIFI_NL80211_SERVICE = "wifinl80211";
field @Deprecated public static final String WIFI_RTT_SERVICE = "rttmanager";
field public static final String WIFI_SCANNING_SERVICE = "wifiscanner";
+ field @FlaggedApi("android.net.wifi.flags.usd") public static final String WIFI_USD_SERVICE = "wifi_usd";
}
public final class ContextParams {
@@ -5209,6 +5210,11 @@ package android.hardware.contexthub {
method @NonNull public java.util.Collection<android.hardware.contexthub.HubServiceInfo> getServiceInfoCollection();
method @Nullable public String getTag();
method public int getVersion();
+ field public static final int REASON_CLOSE_ENDPOINT_SESSION_REQUESTED = 4; // 0x4
+ field public static final int REASON_ENDPOINT_INVALID = 5; // 0x5
+ field public static final int REASON_ENDPOINT_STOPPED = 6; // 0x6
+ field public static final int REASON_FAILURE = 0; // 0x0
+ field public static final int REASON_OPEN_ENDPOINT_SESSION_REQUEST_REJECTED = 3; // 0x3
}
public static final class HubEndpoint.Builder {
@@ -5293,13 +5299,15 @@ package android.hardware.contexthub {
method @NonNull public android.hardware.contexthub.HubServiceInfo build();
}
+ @FlaggedApi("android.chre.flags.offload_api") public interface IHubEndpointDiscoveryCallback {
+ method public void onEndpointsStarted(@NonNull java.util.List<android.hardware.contexthub.HubDiscoveryInfo>);
+ method public void onEndpointsStopped(@NonNull java.util.List<android.hardware.contexthub.HubDiscoveryInfo>, int);
+ }
+
@FlaggedApi("android.chre.flags.offload_api") public interface IHubEndpointLifecycleCallback {
method public void onSessionClosed(@NonNull android.hardware.contexthub.HubEndpointSession, int);
method @NonNull public android.hardware.contexthub.HubEndpointSessionResult onSessionOpenRequest(@NonNull android.hardware.contexthub.HubEndpointInfo, @Nullable android.hardware.contexthub.HubServiceInfo);
method public void onSessionOpened(@NonNull android.hardware.contexthub.HubEndpointSession);
- field public static final int REASON_CLOSE_ENDPOINT_SESSION_REQUESTED = 4; // 0x4
- field public static final int REASON_OPEN_ENDPOINT_SESSION_REQUEST_REJECTED = 3; // 0x3
- field public static final int REASON_UNSPECIFIED = 0; // 0x0
}
@FlaggedApi("android.chre.flags.offload_api") public interface IHubEndpointMessageCallback {
@@ -6315,11 +6323,16 @@ package android.hardware.location {
method @Deprecated public int registerCallback(@NonNull android.hardware.location.ContextHubManager.Callback);
method @Deprecated public int registerCallback(android.hardware.location.ContextHubManager.Callback, android.os.Handler);
method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void registerEndpoint(@NonNull android.hardware.contexthub.HubEndpoint);
+ method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void registerEndpointDiscoveryCallback(long, @NonNull android.hardware.contexthub.IHubEndpointDiscoveryCallback);
+ method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void registerEndpointDiscoveryCallback(long, @NonNull android.hardware.contexthub.IHubEndpointDiscoveryCallback, @NonNull java.util.concurrent.Executor);
+ method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void registerEndpointDiscoveryCallback(@NonNull String, @NonNull android.hardware.contexthub.IHubEndpointDiscoveryCallback);
+ method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void registerEndpointDiscoveryCallback(@NonNull String, @NonNull android.hardware.contexthub.IHubEndpointDiscoveryCallback, @NonNull java.util.concurrent.Executor);
method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public int sendMessage(int, int, @NonNull android.hardware.location.ContextHubMessage);
method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public int unloadNanoApp(int);
method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public android.hardware.location.ContextHubTransaction<java.lang.Void> unloadNanoApp(@NonNull android.hardware.location.ContextHubInfo, long);
method @Deprecated public int unregisterCallback(@NonNull android.hardware.location.ContextHubManager.Callback);
method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void unregisterEndpoint(@NonNull android.hardware.contexthub.HubEndpoint);
+ method @FlaggedApi("android.chre.flags.offload_api") @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public void unregisterEndpointDiscoveryCallback(@NonNull android.hardware.contexthub.IHubEndpointDiscoveryCallback);
field public static final int AUTHORIZATION_DENIED = 0; // 0x0
field public static final int AUTHORIZATION_DENIED_GRACE_PERIOD = 1; // 0x1
field public static final int AUTHORIZATION_GRANTED = 2; // 0x2
@@ -7624,9 +7637,11 @@ package android.media {
method public boolean isActive();
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean isMuted();
method public boolean isSpatialized();
- field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_APP_OPS = 8; // 0x8
+ field @Deprecated @FlaggedApi("android.media.audio.muted_by_port_volume_api") @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_APP_OPS = 8; // 0x8
field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_CLIENT_VOLUME = 16; // 0x10
field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_MASTER = 1; // 0x1
+ field @FlaggedApi("android.media.audio.muted_by_port_volume_api") @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_OP_CONTROL_AUDIO = 128; // 0x80
+ field @FlaggedApi("android.media.audio.muted_by_port_volume_api") @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_OP_PLAY_AUDIO = 8; // 0x8
field @FlaggedApi("android.media.audio.muted_by_port_volume_api") @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_PORT_VOLUME = 64; // 0x40
field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_STREAM_MUTED = 4; // 0x4
field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_STREAM_VOLUME = 2; // 0x2
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 7e0a9b69b7bd..3cbea87e135e 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -130,7 +130,6 @@ import android.util.Slog;
import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.Immutable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.SomeArgs;
import com.android.internal.pm.RoSystemFeatures;
@@ -1020,6 +1019,33 @@ public class ApplicationPackageManager extends PackageManager {
}
}
+ @Override
+ public void setPageSizeAppCompatFlagsSettingsOverride(String packageName, boolean enabled) {
+ try {
+ mPM.setPageSizeAppCompatFlagsSettingsOverride(packageName, enabled);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
+ public boolean isPageSizeCompatEnabled(String packageName) {
+ try {
+ return mPM.isPageSizeCompatEnabled(packageName);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
+ public String getPageSizeCompatWarningMessage(String packageName) {
+ try {
+ return mPM.getPageSizeCompatWarningMessage(packageName);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
private static List<byte[]> encodeCertificates(List<Certificate> certs) throws
CertificateEncodingException {
if (certs == null) {
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 0e68cce7a8f5..7fcae45ea452 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -441,8 +441,8 @@ public class Notification implements Parcelable
/**
* A large-format version of {@link #contentView}, giving the Notification an
- * opportunity to show more detail. The system UI may choose to show this
- * instead of the normal content view at its discretion.
+ * opportunity to show more detail when expanded. The system UI may choose
+ * to show this instead of the normal content view at its discretion.
*
* As of N, this field may be null. The expanded notification view is determined by the
* inputs to {@link Notification.Builder}; a custom RemoteViews can optionally be
@@ -817,13 +817,13 @@ public class Notification implements Parcelable
R.layout.notification_2025_template_expanded_base,
R.layout.notification_2025_template_heads_up_base,
R.layout.notification_2025_template_header,
+ R.layout.notification_2025_template_collapsed_messaging,
+ R.layout.notification_2025_template_collapsed_media,
R.layout.notification_template_material_big_picture,
R.layout.notification_template_material_big_text,
R.layout.notification_template_material_inbox,
- R.layout.notification_template_material_messaging,
R.layout.notification_template_material_big_messaging,
R.layout.notification_template_material_conversation,
- R.layout.notification_template_material_media,
R.layout.notification_template_material_big_media,
R.layout.notification_template_material_call,
R.layout.notification_template_material_big_call,
@@ -1337,7 +1337,7 @@ public class Notification implements Parcelable
public static final String EXTRA_SUMMARY_TEXT = "android.summaryText";
/**
- * {@link #extras} key: this is the longer text shown in the big form of a
+ * {@link #extras} key: this is the longer text shown in the expanded form of a
* {@link BigTextStyle} notification, as supplied to
* {@link BigTextStyle#bigText(CharSequence)}.
*/
@@ -5919,12 +5919,12 @@ public class Notification implements Parcelable
private RemoteViews applyStandardTemplate(int resId, StandardTemplateParams p,
TemplateBindResult result) {
- p.headerless(resId == getBaseLayoutResource()
+ p.headerless(resId == getCollapsedBaseLayoutResource()
|| resId == getHeadsUpBaseLayoutResource()
|| resId == getCompactHeadsUpBaseLayoutResource()
|| resId == getMessagingCompactHeadsUpLayoutResource()
- || resId == getMessagingLayoutResource()
- || resId == R.layout.notification_template_material_media);
+ || resId == getCollapsedMessagingLayoutResource()
+ || resId == getCollapsedMediaLayoutResource());
RemoteViews contentView = new BuilderRemoteViews(mContext.getApplicationInfo(), resId);
resetStandardTemplate(contentView);
@@ -6378,7 +6378,7 @@ public class Notification implements Parcelable
boolean hideSnoozeButton = mN.isFgsOrUij()
|| mN.fullScreenIntent != null
|| isBackgroundColorized(p)
- || p.mViewType != StandardTemplateParams.VIEW_TYPE_BIG;
+ || p.mViewType != StandardTemplateParams.VIEW_TYPE_EXPANDED;
big.setBoolean(R.id.snooze_button, "setEnabled", !hideSnoozeButton);
if (hideSnoozeButton) {
// Only hide; NotificationContentView will show it when it adds the click listener
@@ -6583,19 +6583,21 @@ public class Notification implements Parcelable
.decorationType(StandardTemplateParams.DECORATION_MINIMAL)
.fillTextsFrom(this);
TemplateBindResult result = new TemplateBindResult();
- RemoteViews standard = applyStandardTemplate(getBaseLayoutResource(), p, result);
+ RemoteViews standard = applyStandardTemplate(getCollapsedBaseLayoutResource(),
+ p, result);
buildCustomContentIntoTemplate(mContext, standard, customContent,
p, result);
return standard;
}
- private RemoteViews minimallyDecoratedBigContentView(@NonNull RemoteViews customContent) {
+ private RemoteViews minimallyDecoratedExpandedContentView(
+ @NonNull RemoteViews customContent) {
StandardTemplateParams p = mParams.reset()
- .viewType(StandardTemplateParams.VIEW_TYPE_BIG)
+ .viewType(StandardTemplateParams.VIEW_TYPE_EXPANDED)
.decorationType(StandardTemplateParams.DECORATION_MINIMAL)
.fillTextsFrom(this);
TemplateBindResult result = new TemplateBindResult();
- RemoteViews standard = applyStandardTemplateWithActions(getBigBaseLayoutResource(),
+ RemoteViews standard = applyStandardTemplateWithActions(getExpandedBaseLayoutResource(),
p, result);
buildCustomContentIntoTemplate(mContext, standard, customContent,
p, result);
@@ -6641,7 +6643,7 @@ public class Notification implements Parcelable
StandardTemplateParams p = mParams.reset()
.viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
.fillTextsFrom(this);
- return applyStandardTemplate(getBaseLayoutResource(), p, null /* result */);
+ return applyStandardTemplate(getCollapsedBaseLayoutResource(), p, null /* result */);
}
private boolean useExistingRemoteView(RemoteViews customContent) {
@@ -6679,24 +6681,29 @@ public class Notification implements Parcelable
*/
@Deprecated
public RemoteViews createBigContentView() {
+ return createExpandedContentView();
+ }
+
+ private RemoteViews createExpandedContentView() {
RemoteViews result = null;
if (useExistingRemoteView(mN.bigContentView)) {
return fullyCustomViewRequiresDecoration(false /* fromStyle */)
- ? minimallyDecoratedBigContentView(mN.bigContentView) : mN.bigContentView;
+ ? minimallyDecoratedExpandedContentView(mN.bigContentView)
+ : mN.bigContentView;
}
if (mStyle != null) {
- result = mStyle.makeBigContentView();
+ result = mStyle.makeExpandedContentView();
if (fullyCustomViewRequiresDecoration(true /* fromStyle */)) {
- result = minimallyDecoratedBigContentView(result);
+ result = minimallyDecoratedExpandedContentView(result);
}
}
if (result == null) {
- if (bigContentViewRequired()) {
+ if (expandedContentViewRequired()) {
StandardTemplateParams p = mParams.reset()
- .viewType(StandardTemplateParams.VIEW_TYPE_BIG)
+ .viewType(StandardTemplateParams.VIEW_TYPE_EXPANDED)
.allowTextWithProgress(true)
.fillTextsFrom(this);
- result = applyStandardTemplateWithActions(getBigBaseLayoutResource(), p,
+ result = applyStandardTemplateWithActions(getExpandedBaseLayoutResource(), p,
null /* result */);
}
}
@@ -6710,7 +6717,7 @@ public class Notification implements Parcelable
// apps can detect the change, it's most likely that the changes will simply result in
// visual regressions.
@SuppressWarnings("AndroidFrameworkCompatChange")
- private boolean bigContentViewRequired() {
+ private boolean expandedContentViewRequired() {
if (Flags.notificationExpansionOptional()) {
// Notifications without a bigContentView, style, or actions do not need to expand
boolean exempt = mN.bigContentView == null
@@ -7521,7 +7528,7 @@ public class Notification implements Parcelable
}
@UnsupportedAppUsage
- private int getBaseLayoutResource() {
+ private int getCollapsedBaseLayoutResource() {
if (Flags.notificationsRedesignTemplates()) {
return R.layout.notification_2025_template_collapsed_base;
} else {
@@ -7545,7 +7552,7 @@ public class Notification implements Parcelable
return R.layout.notification_template_material_messaging_compact_heads_up;
}
- private int getBigBaseLayoutResource() {
+ private int getExpandedBaseLayoutResource() {
if (Flags.notificationsRedesignTemplates()) {
return R.layout.notification_2025_template_expanded_base;
} else {
@@ -7565,14 +7572,26 @@ public class Notification implements Parcelable
return R.layout.notification_template_material_inbox;
}
- private int getMessagingLayoutResource() {
- return R.layout.notification_template_material_messaging;
+ private int getCollapsedMessagingLayoutResource() {
+ if (Flags.notificationsRedesignTemplates()) {
+ return R.layout.notification_2025_template_collapsed_messaging;
+ } else {
+ return R.layout.notification_template_material_messaging;
+ }
}
- private int getBigMessagingLayoutResource() {
+ private int getExpandedMessagingLayoutResource() {
return R.layout.notification_template_material_big_messaging;
}
+ private int getCollapsedMediaLayoutResource() {
+ if (Flags.notificationsRedesignTemplates()) {
+ return R.layout.notification_2025_template_collapsed_media;
+ } else {
+ return R.layout.notification_template_material_media;
+ }
+ }
+
private int getConversationLayoutResource() {
return R.layout.notification_template_material_conversation;
}
@@ -8038,7 +8057,7 @@ public class Notification implements Parcelable
protected Builder mBuilder;
/**
- * Overrides ContentTitle in the big form of the template.
+ * Overrides ContentTitle in the expanded form of the template.
* This defaults to the value passed to setContentTitle().
*/
protected void internalSetBigContentTitle(CharSequence title) {
@@ -8046,7 +8065,7 @@ public class Notification implements Parcelable
}
/**
- * Set the first line of text after the detail section in the big form of the template.
+ * Set the first line of text after the detail section in the expanded form of the template.
*/
protected void internalSetSummaryText(CharSequence cs) {
mSummaryText = cs;
@@ -8109,10 +8128,10 @@ public class Notification implements Parcelable
}
/**
- * Construct a Style-specific RemoteViews for the final big notification layout.
+ * Construct a Style-specific RemoteViews for the final expanded notification layout.
* @hide
*/
- public RemoteViews makeBigContentView() {
+ public RemoteViews makeExpandedContentView() {
return null;
}
@@ -8276,7 +8295,7 @@ public class Notification implements Parcelable
}
/**
- * Overrides ContentTitle in the big form of the template.
+ * Overrides ContentTitle in the expanded form of the template.
* This defaults to the value passed to setContentTitle().
*/
@NonNull
@@ -8286,7 +8305,7 @@ public class Notification implements Parcelable
}
/**
- * Set the first line of text after the detail section in the big form of the template.
+ * Set the first line of text after the detail section in the expanded form of the template.
*/
@NonNull
public BigPictureStyle setSummaryText(@Nullable CharSequence cs) {
@@ -8345,7 +8364,7 @@ public class Notification implements Parcelable
}
/**
- * Override the large icon when the big notification is shown.
+ * Override the large icon when the expanded notification is shown.
*/
@NonNull
public BigPictureStyle bigLargeIcon(@Nullable Bitmap b) {
@@ -8353,7 +8372,7 @@ public class Notification implements Parcelable
}
/**
- * Override the large icon when the big notification is shown.
+ * Override the large icon when the expanded notification is shown.
*/
@NonNull
public BigPictureStyle bigLargeIcon(@Nullable Icon icon) {
@@ -8417,7 +8436,7 @@ public class Notification implements Parcelable
.viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
.fillTextsFrom(mBuilder)
.promotedPicture(mPictureIcon);
- return getStandardView(mBuilder.getBaseLayoutResource(), p, null /* result */);
+ return getStandardView(mBuilder.getCollapsedBaseLayoutResource(), p, null /* result */);
}
/**
@@ -8439,7 +8458,7 @@ public class Notification implements Parcelable
/**
* @hide
*/
- public RemoteViews makeBigContentView() {
+ public RemoteViews makeExpandedContentView() {
// Replace mN.mLargeIcon with mBigLargeIcon if mBigLargeIconSet
// This covers the following cases:
// 1. mBigLargeIconSet -> mBigLargeIcon (null or non-null) applies, overrides
@@ -8458,7 +8477,7 @@ public class Notification implements Parcelable
}
StandardTemplateParams p = mBuilder.mParams.reset()
- .viewType(StandardTemplateParams.VIEW_TYPE_BIG).fillTextsFrom(mBuilder);
+ .viewType(StandardTemplateParams.VIEW_TYPE_EXPANDED).fillTextsFrom(mBuilder);
RemoteViews contentView = getStandardView(mBuilder.getBigPictureLayoutResource(),
p, null /* result */);
if (mSummaryTextSet) {
@@ -8605,7 +8624,7 @@ public class Notification implements Parcelable
}
/**
- * Overrides ContentTitle in the big form of the template.
+ * Overrides ContentTitle in the expanded form of the template.
* This defaults to the value passed to setContentTitle().
*/
public BigTextStyle setBigContentTitle(CharSequence title) {
@@ -8614,7 +8633,7 @@ public class Notification implements Parcelable
}
/**
- * Set the first line of text after the detail section in the big form of the template.
+ * Set the first line of text after the detail section in the expanded form of the template.
*/
public BigTextStyle setSummaryText(CharSequence cs) {
internalSetSummaryText(safeCharSequence(cs));
@@ -8622,7 +8641,7 @@ public class Notification implements Parcelable
}
/**
- * Provide the longer text to be displayed in the big form of the
+ * Provide the longer text to be displayed in the expanded form of the
* template in place of the content text.
*/
public BigTextStyle bigText(CharSequence cs) {
@@ -8666,7 +8685,7 @@ public class Notification implements Parcelable
if (increasedHeight) {
ArrayList<Action> originalActions = mBuilder.mActions;
mBuilder.mActions = new ArrayList<>();
- RemoteViews remoteViews = makeBigContentView();
+ RemoteViews remoteViews = makeExpandedContentView();
mBuilder.mActions = originalActions;
return remoteViews;
}
@@ -8680,7 +8699,7 @@ public class Notification implements Parcelable
public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
if (increasedHeight && mBuilder.mActions.size() > 0) {
// TODO(b/163626038): pass VIEW_TYPE_HEADS_UP?
- return makeBigContentView();
+ return makeExpandedContentView();
}
return super.makeHeadsUpContentView(increasedHeight);
}
@@ -8688,9 +8707,9 @@ public class Notification implements Parcelable
/**
* @hide
*/
- public RemoteViews makeBigContentView() {
+ public RemoteViews makeExpandedContentView() {
StandardTemplateParams p = mBuilder.mParams.reset()
- .viewType(StandardTemplateParams.VIEW_TYPE_BIG)
+ .viewType(StandardTemplateParams.VIEW_TYPE_EXPANDED)
.allowTextWithProgress(true)
.textViewId(R.id.big_text)
.fillTextsFrom(mBuilder);
@@ -9362,20 +9381,20 @@ public class Notification implements Parcelable
* @hide
*/
@Override
- public RemoteViews makeBigContentView() {
- return makeMessagingView(StandardTemplateParams.VIEW_TYPE_BIG);
+ public RemoteViews makeExpandedContentView() {
+ return makeMessagingView(StandardTemplateParams.VIEW_TYPE_EXPANDED);
}
/**
* Create a messaging layout.
*
- * @param viewType one of StandardTemplateParams.VIEW_TYPE_NORMAL, VIEW_TYPE_BIG,
+ * @param viewType one of StandardTemplateParams.VIEW_TYPE_NORMAL, VIEW_TYPE_EXPANDEDIG,
* VIEW_TYPE_HEADS_UP
* @return the created remoteView.
*/
@NonNull
private RemoteViews makeMessagingView(int viewType) {
- boolean isCollapsed = viewType != StandardTemplateParams.VIEW_TYPE_BIG;
+ boolean isCollapsed = viewType != StandardTemplateParams.VIEW_TYPE_EXPANDED;
boolean hideRightIcons = viewType != StandardTemplateParams.VIEW_TYPE_NORMAL;
boolean isConversationLayout = mConversationType != CONVERSATION_TYPE_LEGACY;
boolean isImportantConversation = mConversationType == CONVERSATION_TYPE_IMPORTANT;
@@ -9419,8 +9438,8 @@ public class Notification implements Parcelable
isConversationLayout
? mBuilder.getConversationLayoutResource()
: isCollapsed
- ? mBuilder.getMessagingLayoutResource()
- : mBuilder.getBigMessagingLayoutResource(),
+ ? mBuilder.getCollapsedMessagingLayoutResource()
+ : mBuilder.getExpandedMessagingLayoutResource(),
p,
bindResult);
if (isConversationLayout) {
@@ -10043,7 +10062,7 @@ public class Notification implements Parcelable
}
/**
- * Overrides ContentTitle in the big form of the template.
+ * Overrides ContentTitle in the expanded form of the template.
* This defaults to the value passed to setContentTitle().
*/
public InboxStyle setBigContentTitle(CharSequence title) {
@@ -10052,7 +10071,7 @@ public class Notification implements Parcelable
}
/**
- * Set the first line of text after the detail section in the big form of the template.
+ * Set the first line of text after the detail section in the expanded form of the template.
*/
public InboxStyle setSummaryText(CharSequence cs) {
internalSetSummaryText(safeCharSequence(cs));
@@ -10100,9 +10119,9 @@ public class Notification implements Parcelable
/**
* @hide
*/
- public RemoteViews makeBigContentView() {
+ public RemoteViews makeExpandedContentView() {
StandardTemplateParams p = mBuilder.mParams.reset()
- .viewType(StandardTemplateParams.VIEW_TYPE_BIG)
+ .viewType(StandardTemplateParams.VIEW_TYPE_EXPANDED)
.fillTextsFrom(mBuilder).text(null);
TemplateBindResult result = new TemplateBindResult();
RemoteViews contentView = getStandardView(mBuilder.getInboxLayoutResource(), p, result);
@@ -10357,8 +10376,8 @@ public class Notification implements Parcelable
* @hide
*/
@Override
- public RemoteViews makeBigContentView() {
- return makeMediaBigContentView(null /* customContent */);
+ public RemoteViews makeExpandedContentView() {
+ return makeMediaExpandedContentView(null /* customContent */);
}
/**
@@ -10473,7 +10492,7 @@ public class Notification implements Parcelable
.fillTextsFrom(mBuilder);
TemplateBindResult result = new TemplateBindResult();
RemoteViews template = mBuilder.applyStandardTemplate(
- R.layout.notification_template_material_media, p,
+ mBuilder.getCollapsedMediaLayoutResource(), p,
null /* result */);
for (int i = 0; i < MAX_MEDIA_BUTTONS_IN_COMPACT; i++) {
@@ -10494,10 +10513,10 @@ public class Notification implements Parcelable
}
/** @hide */
- protected RemoteViews makeMediaBigContentView(@Nullable RemoteViews customContent) {
+ protected RemoteViews makeMediaExpandedContentView(@Nullable RemoteViews customContent) {
final int actionCount = Math.min(mBuilder.mActions.size(), MAX_MEDIA_BUTTONS);
StandardTemplateParams p = mBuilder.mParams.reset()
- .viewType(StandardTemplateParams.VIEW_TYPE_BIG)
+ .viewType(StandardTemplateParams.VIEW_TYPE_EXPANDED)
.hideProgress(true)
.fillTextsFrom(mBuilder);
TemplateBindResult result = new TemplateBindResult();
@@ -10803,8 +10822,8 @@ public class Notification implements Parcelable
/**
* @hide
*/
- public RemoteViews makeBigContentView() {
- return makeCallLayout(StandardTemplateParams.VIEW_TYPE_BIG);
+ public RemoteViews makeExpandedContentView() {
+ return makeCallLayout(StandardTemplateParams.VIEW_TYPE_EXPANDED);
}
@NonNull
@@ -11550,7 +11569,7 @@ public class Notification implements Parcelable
.hideProgress(true)
.fillTextsFrom(mBuilder);
- return getStandardView(mBuilder.getBaseLayoutResource(), p, null /* result */);
+ return getStandardView(mBuilder.getCollapsedBaseLayoutResource(), p, null /* result */);
}
/**
* @hide
@@ -11568,9 +11587,9 @@ public class Notification implements Parcelable
* @hide
*/
@Override
- public RemoteViews makeBigContentView() {
+ public RemoteViews makeExpandedContentView() {
StandardTemplateParams p = mBuilder.mParams.reset()
- .viewType(StandardTemplateParams.VIEW_TYPE_BIG)
+ .viewType(StandardTemplateParams.VIEW_TYPE_EXPANDED)
.allowTextWithProgress(true)
.hideProgress(true)
.fillTextsFrom(mBuilder);
@@ -12014,8 +12033,8 @@ public class Notification implements Parcelable
* @hide
*/
@Override
- public RemoteViews makeBigContentView() {
- return makeDecoratedBigContentView();
+ public RemoteViews makeExpandedContentView() {
+ return makeDecoratedExpandedContentView();
}
/**
@@ -12058,13 +12077,13 @@ public class Notification implements Parcelable
.decorationType(StandardTemplateParams.DECORATION_PARTIAL)
.fillTextsFrom(mBuilder);
RemoteViews remoteViews = mBuilder.applyStandardTemplate(
- mBuilder.getBaseLayoutResource(), p, result);
+ mBuilder.getCollapsedBaseLayoutResource(), p, result);
buildCustomContentIntoTemplate(mBuilder.mContext, remoteViews, customContent,
p, result);
return remoteViews;
}
- private RemoteViews makeDecoratedBigContentView() {
+ private RemoteViews makeDecoratedExpandedContentView() {
RemoteViews bigContentView = mBuilder.mN.bigContentView == null
? mBuilder.mN.contentView
: mBuilder.mN.bigContentView;
@@ -12073,11 +12092,11 @@ public class Notification implements Parcelable
}
TemplateBindResult result = new TemplateBindResult();
StandardTemplateParams p = mBuilder.mParams.reset()
- .viewType(StandardTemplateParams.VIEW_TYPE_BIG)
+ .viewType(StandardTemplateParams.VIEW_TYPE_EXPANDED)
.decorationType(StandardTemplateParams.DECORATION_PARTIAL)
.fillTextsFrom(mBuilder);
RemoteViews remoteViews = mBuilder.applyStandardTemplateWithActions(
- mBuilder.getBigBaseLayoutResource(), p, result);
+ mBuilder.getExpandedBaseLayoutResource(), p, result);
buildCustomContentIntoTemplate(mBuilder.mContext, remoteViews, bigContentView,
p, result);
return remoteViews;
@@ -12150,11 +12169,11 @@ public class Notification implements Parcelable
* @hide
*/
@Override
- public RemoteViews makeBigContentView() {
+ public RemoteViews makeExpandedContentView() {
RemoteViews customContent = mBuilder.mN.bigContentView != null
? mBuilder.mN.bigContentView
: mBuilder.mN.contentView;
- return makeMediaBigContentView(customContent);
+ return makeMediaExpandedContentView(customContent);
}
/**
@@ -12165,7 +12184,7 @@ public class Notification implements Parcelable
RemoteViews customContent = mBuilder.mN.headsUpContentView != null
? mBuilder.mN.headsUpContentView
: mBuilder.mN.contentView;
- return makeMediaBigContentView(customContent);
+ return makeMediaExpandedContentView(customContent);
}
/**
@@ -14491,7 +14510,7 @@ public class Notification implements Parcelable
public static int VIEW_TYPE_UNSPECIFIED = 0;
public static int VIEW_TYPE_NORMAL = 1;
- public static int VIEW_TYPE_BIG = 2;
+ public static int VIEW_TYPE_EXPANDED = 2;
public static int VIEW_TYPE_HEADS_UP = 3;
public static int VIEW_TYPE_MINIMIZED = 4; // header only for minimized state
public static int VIEW_TYPE_PUBLIC = 5; // header only for automatic public version
diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java
index e218418336c5..3973c58c0708 100644
--- a/core/java/android/app/PropertyInvalidatedCache.java
+++ b/core/java/android/app/PropertyInvalidatedCache.java
@@ -61,6 +61,8 @@ import java.util.Random;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
/**
@@ -680,12 +682,17 @@ public class PropertyInvalidatedCache<Query, Result> {
@GuardedBy("mLock")
private boolean mTestMode = false;
- /**
- * The local value of the handler, used during testing but also used directly by the
- * NonceLocal handler.
- */
+ // This is the local value of the nonce, as last set by the NonceHandler. It is always
+ // updated by the setNonce() operation. The getNonce() operation returns this value in
+ // NonceLocal handlers and handlers in test mode.
+ @GuardedBy("mLock")
+ protected long mShadowNonce = NONCE_UNSET;
+
+ // A list of watchers to be notified of changes. This is null until at least one watcher
+ // registers. Checking for null is meant to be the fastest way the handler can determine
+ // that there are no watchers to be notified.
@GuardedBy("mLock")
- protected long mTestNonce = NONCE_UNSET;
+ private ArrayList<Semaphore> mWatchers;
/**
* The methods to get and set a nonce from whatever storage is being used. mLock may be
@@ -701,27 +708,60 @@ public class PropertyInvalidatedCache<Query, Result> {
/**
* Get a nonce from storage. If the handler is in test mode, the nonce is returned from
- * the local mTestNonce.
+ * the local mShadowNonce.
*/
long getNonce() {
synchronized (mLock) {
- if (mTestMode) return mTestNonce;
+ if (mTestMode) return mShadowNonce;
}
return getNonceInternal();
}
/**
- * Write a nonce to storage. If the handler is in test mode, the nonce is written to the
- * local mTestNonce and storage is not affected.
+ * Write a nonce to storage. The nonce is always written to the local mShadowNonce. If
+ * the handler is not in test mode the nonce is also written to storage.
*/
void setNonce(long val) {
synchronized (mLock) {
- if (mTestMode) {
- mTestNonce = val;
- return;
+ mShadowNonce = val;
+ if (!mTestMode) {
+ setNonceInternal(val);
+ }
+ wakeAllWatchersLocked();
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void wakeAllWatchersLocked() {
+ if (mWatchers != null) {
+ for (int i = 0; i < mWatchers.size(); i++) {
+ mWatchers.get(i).release();
+ }
+ }
+ }
+
+ /**
+ * Register a watcher to be notified when a nonce changes. There is no check for
+ * duplicates. In general, this method is called only from {@link NonceWatcher}.
+ */
+ void registerWatcher(Semaphore s) {
+ synchronized (mLock) {
+ if (mWatchers == null) {
+ mWatchers = new ArrayList<>();
+ }
+ mWatchers.add(s);
+ }
+ }
+
+ /**
+ * Unregister a watcher. Nothing happens if the watcher is not registered.
+ */
+ void unregisterWatcher(Semaphore s) {
+ synchronized (mLock) {
+ if (mWatchers != null) {
+ mWatchers.remove(s);
}
}
- setNonceInternal(val);
}
/**
@@ -854,7 +894,7 @@ public class PropertyInvalidatedCache<Query, Result> {
void setTestMode(boolean mode) {
synchronized (mLock) {
mTestMode = mode;
- mTestNonce = NONCE_UNSET;
+ mShadowNonce = NONCE_UNSET;
}
}
@@ -1028,7 +1068,7 @@ public class PropertyInvalidatedCache<Query, Result> {
/**
* SystemProperties and shared storage are protected and cannot be written by random
* processes. So, for testing purposes, the NonceLocal handler stores the nonce locally. The
- * NonceLocal uses the mTestNonce in the superclass, regardless of test mode.
+ * NonceLocal uses the mShadowNonce in the superclass, regardless of test mode.
*/
private static class NonceLocal extends NonceHandler {
// The saved nonce.
@@ -1040,16 +1080,130 @@ public class PropertyInvalidatedCache<Query, Result> {
@Override
long getNonceInternal() {
- return mTestNonce;
+ return mShadowNonce;
}
@Override
void setNonceInternal(long value) {
- mTestNonce = value;
+ mShadowNonce = value;
+ }
+ }
+
+ /**
+ * A NonceWatcher lets an external client test if a nonce value has changed from the last time
+ * the watcher was checked.
+ * @hide
+ */
+ public static class NonceWatcher implements AutoCloseable {
+ // The handler for the key.
+ private final NonceHandler mHandler;
+
+ // The last-seen value. This is initialized to "unset".
+ private long mLastSeen = NONCE_UNSET;
+
+ // The semaphore that the watcher waits on. A permit is released every time the nonce
+ // changes. Permits are acquired in the wait method.
+ private final Semaphore mSem = new Semaphore(0);
+
+ /**
+ * Create a watcher for a handler. The last-seen value is not set here and will be
+ * "unset". Therefore, a call to isChanged() will return true if the nonce has ever been
+ * set, no matter when the watcher is first created. Clients may want to flush that
+ * change by calling isChanged() immediately after constructing the object.
+ */
+ private NonceWatcher(@NonNull NonceHandler handler) {
+ mHandler = handler;
+ mHandler.registerWatcher(mSem);
+ }
+
+ /**
+ * Unregister to be notified when a nonce changes. NonceHandler allows a call to
+ * unregisterWatcher with a semaphore that is not registered, so there is no check inside
+ * this method to guard against multiple closures.
+ */
+ @Override
+ public void close() {
+ mHandler.unregisterWatcher(mSem);
+ }
+
+ /**
+ * Return the last seen value of the nonce. This does not update that value. Only
+ * {@link #isChanged()} updates the value.
+ */
+ public long lastSeen() {
+ return mLastSeen;
+ }
+
+ /**
+ * Return true if the nonce has changed from the last time isChanged() was called. The
+ * method is not thread safe.
+ * @hide
+ */
+ public boolean isChanged() {
+ long current = mHandler.getNonce();
+ if (current != mLastSeen) {
+ mLastSeen = current;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Wait for the nonce value to change. It is not guaranteed that the nonce has changed when
+ * this returns: clients must confirm with {@link #isChanged}. The wait operation is only
+ * effective in a process that writes the nonces. The function returns the number of times
+ * the nonce had changed since the last call to the method.
+ * @hide
+ */
+ public int waitForChange() throws InterruptedException {
+ mSem.acquire(1);
+ return 1 + mSem.drainPermits();
+ }
+
+ /**
+ * Wait for the nonce value to change. It is not guaranteed that the nonce has changed when
+ * this returns: clients must confirm with {@link #isChanged}. The wait operation is only
+ * effective in a process that writes the nonces. The function returns the number of times
+ * the nonce changed since the last call to the method. A return value of zero means the
+ * timeout expired. Beware that a timeout of 0 means the function will not wait at all.
+ * @hide
+ */
+ public int waitForChange(long timeout, TimeUnit timeUnit) throws InterruptedException {
+ if (mSem.tryAcquire(1, timeout, timeUnit)) {
+ return 1 + mSem.drainPermits();
+ } else {
+ return 0;
+ }
+ }
+
+ /**
+ * Wake the watcher by releasing the semaphore. This can be used to wake clients that are
+ * blocked in {@link #waitForChange} without affecting the underlying nonce.
+ * @hide
+ */
+ public void wakeUp() {
+ mSem.release();
}
}
/**
+ * Return a NonceWatcher for the cache.
+ * @hide
+ */
+ public NonceWatcher getNonceWatcher() {
+ return new NonceWatcher(mNonce);
+ }
+
+ /**
+ * Return a NonceWatcher for the given property. If a handler does not exist for the
+ * property, one is created. This throws if the property name is not a valid cache key.
+ * @hide
+ */
+ public static NonceWatcher getNonceWatcher(@NonNull String propertyName) {
+ return new NonceWatcher(getNonceHandler(propertyName));
+ }
+
+ /**
* Complete key prefixes.
*/
private static final String PREFIX_TEST = CACHE_KEY_PREFIX + "." + MODULE_TEST + ".";
@@ -1663,6 +1817,26 @@ public class PropertyInvalidatedCache<Query, Result> {
}
/**
+ * Non-static version of corkInvalidations() for situations in which the cache instance is
+ * available. This is slightly faster than than the static versions because it does not have
+ * to look up the NonceHandler for a given property name.
+ * @hide
+ */
+ public void corkInvalidations() {
+ mNonce.cork();
+ }
+
+ /**
+ * Non-static version of uncorkInvalidations() for situations in which the cache instance is
+ * available. This is slightly faster than than the static versions because it does not have
+ * to look up the NonceHandler for a given property name.
+ * @hide
+ */
+ public void uncorkInvalidations() {
+ mNonce.uncork();
+ }
+
+ /**
* Invalidate caches in all processes that are keyed for the module and api.
* @hide
*/
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 42fa9e7d7994..74d729857cc8 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -4177,6 +4177,35 @@ public class DevicePolicyManager {
}
}
+
+ /**
+ * Similar to the public variant of {@link #setMtePolicy} but for use by the system.
+ *
+ * <p>Called by a system service only, meaning that the caller's UID must be equal to
+ * {@link Process#SYSTEM_UID}.
+ *
+ * @throws SecurityException if caller is not permitted to set Mte policy
+ * @throws UnsupportedOperationException if the device does not support MTE
+ * @param systemEntity The service entity that adds the restriction. A user restriction set by
+ * a service entity can only be cleared by the same entity. This can be
+ * just the calling package name, or any string of the caller's choice
+ * can be used.
+ * @param policy the MTE policy to be set
+ * @hide
+ */
+ public void setMtePolicy(@NonNull String systemEntity, @MtePolicy int policy) {
+ throwIfParentInstance("setMtePolicyForUser");
+ if (mService != null) {
+ try {
+ mService.setMtePolicyBySystem(systemEntity, policy);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+
+
/**
* Called by a device owner, profile owner of an organization-owned device to
* get the Memory Tagging Extension (MTE) policy
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index d048b5371fc4..e7be822d52d3 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -617,6 +617,7 @@ interface IDevicePolicyManager {
int[] getApplicationExemptions(String packageName);
void setMtePolicy(int flag, String callerPackageName);
+ void setMtePolicyBySystem(in String systemEntity, int policy);
int getMtePolicy(String callerPackageName);
void setManagedSubscriptionsPolicy(in ManagedSubscriptionsPolicy policy);
diff --git a/core/java/android/companion/CompanionDeviceService.java b/core/java/android/companion/CompanionDeviceService.java
index 5ad2348254e2..db080fcc7702 100644
--- a/core/java/android/companion/CompanionDeviceService.java
+++ b/core/java/android/companion/CompanionDeviceService.java
@@ -249,7 +249,7 @@ public abstract class CompanionDeviceService extends Service {
// TODO(b/315163162) Add @Deprecated keyword after 24Q2 cut.
/**
- * Called by system whenever a device associated with this app is connected.
+ * Called by the system when an associated device is nearby or connected.
*
* @param associationInfo A record for the companion device.
*/
@@ -262,7 +262,7 @@ public abstract class CompanionDeviceService extends Service {
// TODO(b/315163162) Add @Deprecated keyword after 24Q2 cut.
/**
- * Called by system whenever a device associated with this app is disconnected.
+ * Called by the system when an associated device is out of range or disconnected.
*
* @param associationInfo A record for the companion device.
*/
@@ -274,7 +274,7 @@ public abstract class CompanionDeviceService extends Service {
}
/**
- * Called by the system during device events.
+ * Called by the system when an associated device's presence state changes.
*
* @see CompanionDeviceManager#startObservingDevicePresence(ObservingDevicePresenceRequest)
*/
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index acad92c90c59..6e2ca2c109f1 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -4250,6 +4250,7 @@ public abstract class Context {
//@hide: WIFI_RTT_SERVICE,
//@hide: ETHERNET_SERVICE,
WIFI_RTT_RANGING_SERVICE,
+ WIFI_USD_SERVICE,
NSD_SERVICE,
AUDIO_SERVICE,
AUDIO_DEVICE_VOLUME_SERVICE,
@@ -5096,6 +5097,19 @@ public abstract class Context {
*/
public static final String WIFI_RTT_RANGING_SERVICE = "wifirtt";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a {@link
+ * android.net.wifi.usd.UsdManager} for Unsynchronized Service Discovery (USD) operation.
+ *
+ * @see #getSystemService(String)
+ * @see android.net.wifi.usd.UsdManager
+ * @hide
+ */
+ @FlaggedApi(android.net.wifi.flags.Flags.FLAG_USD)
+ @SystemApi
+ public static final String WIFI_USD_SERVICE = "wifi_usd";
+
/**
* Use with {@link #getSystemService(String)} to retrieve a {@link
* android.net.lowpan.LowpanManager} for handling management of
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index cccfdb0938e5..94784227049d 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -1449,6 +1449,97 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
}
}
+ /**
+ * Use this to report any errors during alignment checks
+ *
+ * @hide
+ */
+ public static final int PAGE_SIZE_APP_COMPAT_FLAG_ERROR = -1;
+
+ /**
+ * Initial value for mPageSizeAppCompatFlags
+ *
+ * @hide
+ */
+ public static final int PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED = 0;
+
+ /**
+ * if set, extract libs forcefully for 16 KB device and show warning dialog.
+ *
+ * @hide
+ */
+ public static final int PAGE_SIZE_APP_COMPAT_FLAG_UNCOMPRESSED_LIBS_NOT_ALIGNED = 1 << 1;
+
+ /**
+ * if set, load 4 KB aligned ELFs on 16 KB device in compat mode and show warning dialog.
+ *
+ * @hide
+ */
+ public static final int PAGE_SIZE_APP_COMPAT_FLAG_ELF_NOT_ALIGNED = 1 << 2;
+
+ /**
+ * Run in 16 KB app compat mode. This flag will be set explicitly through settings. If set, 16
+ * KB app compat warning dialogs will still show up.
+ *
+ * @hide
+ */
+ public static final int PAGE_SIZE_APP_COMPAT_FLAG_SETTINGS_OVERRIDE_ENABLED = 1 << 3;
+
+ /**
+ * Disable 16 KB app compat mode through settings. It should only affect ELF loading as app is
+ * already installed.
+ *
+ * @hide
+ */
+ public static final int PAGE_SIZE_APP_COMPAT_FLAG_SETTINGS_OVERRIDE_DISABLED = 1 << 4;
+
+ /**
+ * Run in 16 KB app compat mode. This flag will be set explicitly through manifest. If set, hide
+ * the 16 KB app compat warning dialogs. This has the highest priority to enable compat mode.
+ *
+ * @hide
+ */
+ public static final int PAGE_SIZE_APP_COMPAT_FLAG_MANIFEST_OVERRIDE_ENABLED = 1 << 5;
+
+ /**
+ * Disable 16 KB app compat mode. This has the highest priority to disable compat mode.
+ *
+ * @hide
+ */
+ public static final int PAGE_SIZE_APP_COMPAT_FLAG_MANIFEST_OVERRIDE_DISABLED = 1 << 6;
+
+ /**
+ * Max value for page size app compat
+ *
+ * @hide
+ */
+ public static final int PAGE_SIZE_APP_COMPAT_FLAG_MAX = 1 << 7;
+
+ /**
+ * 16 KB app compat status for the app. App can have native shared libs which are not page
+ * aligned, LOAD segments inside the shared libs have to be page aligned. Apps can specify the
+ * override in manifest file as well.
+ */
+ private @PageSizeAppCompatFlags int mPageSizeAppCompatFlags =
+ ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED;
+
+ /** {@hide} */
+ @IntDef(
+ prefix = {"PAGE_SIZE_APP_COMPAT_FLAG_"},
+ value = {
+ PAGE_SIZE_APP_COMPAT_FLAG_ERROR,
+ PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED,
+ PAGE_SIZE_APP_COMPAT_FLAG_UNCOMPRESSED_LIBS_NOT_ALIGNED,
+ PAGE_SIZE_APP_COMPAT_FLAG_ELF_NOT_ALIGNED,
+ PAGE_SIZE_APP_COMPAT_FLAG_MANIFEST_OVERRIDE_ENABLED,
+ PAGE_SIZE_APP_COMPAT_FLAG_MANIFEST_OVERRIDE_DISABLED,
+ PAGE_SIZE_APP_COMPAT_FLAG_SETTINGS_OVERRIDE_ENABLED,
+ PAGE_SIZE_APP_COMPAT_FLAG_SETTINGS_OVERRIDE_DISABLED,
+ PAGE_SIZE_APP_COMPAT_FLAG_MAX,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PageSizeAppCompatFlags {}
+
/** @hide */
public String classLoaderName;
@@ -1777,7 +1868,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
pw.println(prefix + "enableOnBackInvokedCallback=" + isOnBackInvokedCallbackEnabled());
pw.println(prefix + "allowCrossUidActivitySwitchFromBelow="
+ allowCrossUidActivitySwitchFromBelow);
-
+ pw.println(prefix + "mPageSizeAppCompatFlags=" + mPageSizeAppCompatFlags);
}
pw.println(prefix + "createTimestamp=" + createTimestamp);
if (mKnownActivityEmbeddingCerts != null) {
@@ -1897,6 +1988,10 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
}
proto.write(ApplicationInfoProto.Detail.ALLOW_CROSS_UID_ACTIVITY_SWITCH_FROM_BELOW,
allowCrossUidActivitySwitchFromBelow);
+
+ proto.write(ApplicationInfoProto.Detail.ENABLE_PAGE_SIZE_APP_COMPAT,
+ mPageSizeAppCompatFlags);
+
proto.end(detailToken);
}
if (!ArrayUtils.isEmpty(mKnownActivityEmbeddingCerts)) {
@@ -2024,6 +2119,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
localeConfigRes = orig.localeConfigRes;
allowCrossUidActivitySwitchFromBelow = orig.allowCrossUidActivitySwitchFromBelow;
createTimestamp = SystemClock.uptimeMillis();
+ mPageSizeAppCompatFlags = orig.mPageSizeAppCompatFlags;
}
public String toString() {
@@ -2128,6 +2224,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
}
dest.writeInt(localeConfigRes);
dest.writeInt(allowCrossUidActivitySwitchFromBelow ? 1 : 0);
+ dest.writeInt(mPageSizeAppCompatFlags);
sForStringSet.parcel(mKnownActivityEmbeddingCerts, dest, flags);
}
@@ -2228,6 +2325,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
}
localeConfigRes = source.readInt();
allowCrossUidActivitySwitchFromBelow = source.readInt() != 0;
+ mPageSizeAppCompatFlags = source.readInt();
mKnownActivityEmbeddingCerts = sForStringSet.unparcel(source);
if (mKnownActivityEmbeddingCerts.isEmpty()) {
@@ -2765,6 +2863,11 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
requestRawExternalStorageAccess = value;
}
+ /** {@hide} */
+ public void setPageSizeAppCompatFlags(@PageSizeAppCompatFlags int value) {
+ mPageSizeAppCompatFlags |= value;
+ }
+
/**
* Replaces {@link #mAppClassNamesByProcess}. This takes over the ownership of the passed map.
* Do not modify the argument at the callsite.
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 5d4babb8a36d..9f898b823a76 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -848,4 +848,12 @@ interface IPackageManager {
int getAppMetadataSource(String packageName, int userId);
ComponentName getDomainVerificationAgent(int userId);
+
+ void setPageSizeAppCompatFlagsSettingsOverride(in String packageName, boolean enabled);
+
+ boolean isPageSizeCompatEnabled(in String packageName);
+
+ String getPageSizeCompatWarningMessage(in String packageName);
+
+ List<String> getAllApexDirectories();
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index d2b43b9bd2b4..23d3693628e7 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -804,7 +804,6 @@ public abstract class PackageManager {
@Deprecated
private void __metadata() {}
-
//@formatter:on
// End of generated code
@@ -6729,6 +6728,11 @@ public abstract class PackageManager {
* If the given permission already exists, the info you supply here
* will be used to update it.
*
+ * @deprecated Support for dynamic permissions is going to be removed, and apps that use dynamic
+ * permissions should declare their permissions statically inside their app manifest instead.
+ * This method will become a no-op in a future Android release and eventually be removed from
+ * the SDK.
+ *
* @param info Description of the permission to be added.
*
* @return Returns true if a new permission was created, false if an
@@ -6739,7 +6743,9 @@ public abstract class PackageManager {
*
* @see #removePermission(String)
*/
- //@Deprecated
+ @SuppressWarnings("HiddenAbstractMethod")
+ @FlaggedApi(android.permission.flags.Flags.FLAG_PERMISSION_TREE_APIS_DEPRECATED)
+ @Deprecated
public abstract boolean addPermission(@NonNull PermissionInfo info);
/**
@@ -6748,8 +6754,15 @@ public abstract class PackageManager {
* allowing it to return quicker and batch a series of adds at the
* expense of no guarantee the added permission will be retained if
* the device is rebooted before it is written.
+ *
+ * @deprecated Support for dynamic permissions is going to be removed, and apps that use dynamic
+ * permissions should declare their permissions statically inside their app manifest instead.
+ * This method will become a no-op in a future Android release and eventually be removed from
+ * the SDK.
*/
- //@Deprecated
+ @SuppressWarnings("HiddenAbstractMethod")
+ @FlaggedApi(android.permission.flags.Flags.FLAG_PERMISSION_TREE_APIS_DEPRECATED)
+ @Deprecated
public abstract boolean addPermissionAsync(@NonNull PermissionInfo info);
/**
@@ -6758,6 +6771,11 @@ public abstract class PackageManager {
* -- you are only allowed to remove permissions that you are allowed
* to add.
*
+ * @deprecated Support for dynamic permissions is going to be removed, and apps that use dynamic
+ * permissions should declare their permissions statically inside their app manifest instead.
+ * This method will become a no-op in a future Android release and eventually be removed from
+ * the SDK.
+ *
* @param permName The name of the permission to remove.
*
* @throws SecurityException if you are not allowed to remove the
@@ -6765,7 +6783,9 @@ public abstract class PackageManager {
*
* @see #addPermission(PermissionInfo)
*/
- //@Deprecated
+ @SuppressWarnings("HiddenAbstractMethod")
+ @FlaggedApi(android.permission.flags.Flags.FLAG_PERMISSION_TREE_APIS_DEPRECATED)
+ @Deprecated
public abstract void removePermission(@NonNull String permName);
/**
@@ -10987,6 +11007,41 @@ public abstract class PackageManager {
}
/**
+ * Set the page compat mode override for given package
+ *
+ * @hide
+ */
+ @FlaggedApi(android.content.pm.Flags.FLAG_APP_COMPAT_OPTION_16KB)
+ public void setPageSizeAppCompatFlagsSettingsOverride(@NonNull String packageName,
+ boolean enabled) {
+ throw new UnsupportedOperationException(
+ "setPageSizeAppCompatFlagsSettingsOverride not implemented in subclass");
+ }
+
+ /**
+ * Check whether page size app compat mode is enabled for given package
+ *
+ * @hide
+ */
+ @FlaggedApi(android.content.pm.Flags.FLAG_APP_COMPAT_OPTION_16KB)
+ public boolean isPageSizeCompatEnabled(@NonNull String packageName) {
+ throw new UnsupportedOperationException(
+ "isPageSizeCompatEnabled not implemented in subclass");
+ }
+
+ /**
+ * Get the page size app compat warning dialog to show at app launch time
+ *
+ * @hide
+ */
+ @Nullable
+ @FlaggedApi(android.content.pm.Flags.FLAG_APP_COMPAT_OPTION_16KB)
+ public String getPageSizeCompatWarningMessage(@NonNull String packageName) {
+ throw new UnsupportedOperationException(
+ "getPageSizeCompatWarningMessage not implemented in subclass");
+ }
+
+ /**
* Returns the harmful app warning string for the given app, or null if there is none set.
*
* @param packageName The full name of the desired package.
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 813208d7ff38..833260a15c45 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -341,6 +341,28 @@ flag {
is_fixed_read_only: true
}
+flag {
+ name: "cache_user_start_realtime_read_only"
+ namespace: "multiuser"
+ description: "Cache getUserStartRealtime to avoid unnecessary binder calls"
+ bug: "350416205"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+ is_fixed_read_only: true
+}
+
+flag {
+ name: "cache_user_unlock_realtime_read_only"
+ namespace: "multiuser"
+ description: "Cache getUserUnlockRealtime to avoid unnecessary binder calls"
+ bug: "350421407"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+ is_fixed_read_only: true
+}
+
# This flag guards the private space feature and all its implementations excluding the APIs. APIs are guarded by android.os.Flags.allow_private_profile.
flag {
name: "enable_private_space_features"
diff --git a/core/java/android/hardware/contexthub/HubEndpoint.java b/core/java/android/hardware/contexthub/HubEndpoint.java
index 078b4d4629e0..7efdd6dbdf41 100644
--- a/core/java/android/hardware/contexthub/HubEndpoint.java
+++ b/core/java/android/hardware/contexthub/HubEndpoint.java
@@ -18,6 +18,7 @@ package android.hardware.contexthub;
import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
@@ -31,6 +32,8 @@ import android.util.SparseArray;
import androidx.annotation.GuardedBy;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -48,6 +51,46 @@ import java.util.concurrent.Executor;
public class HubEndpoint {
private static final String TAG = "HubEndpoint";
+ /**
+ * Constants describing the outcome of operations through HubEndpoints (like opening/closing of
+ * sessions or stopping of endpoints).
+ *
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(
+ prefix = {"REASON_"},
+ value = {
+ REASON_FAILURE,
+ REASON_OPEN_ENDPOINT_SESSION_REQUEST_REJECTED,
+ REASON_CLOSE_ENDPOINT_SESSION_REQUESTED,
+ REASON_ENDPOINT_INVALID,
+ REASON_ENDPOINT_STOPPED,
+ })
+ public @interface Reason {}
+
+ /** Unclassified failure */
+ public static final int REASON_FAILURE = 0;
+
+ // The values 1 and 2 are reserved at the Context Hub HAL but not exposed to apps.
+
+ /** The peer rejected the request to open this endpoint session. */
+ public static final int REASON_OPEN_ENDPOINT_SESSION_REQUEST_REJECTED = 3;
+
+ /** The peer closed this endpoint session. */
+ public static final int REASON_CLOSE_ENDPOINT_SESSION_REQUESTED = 4;
+
+ /** The peer endpoint is invalid. */
+ public static final int REASON_ENDPOINT_INVALID = 5;
+
+ /**
+ * The endpoint is now stopped. The app should retrieve the endpoint info using {@link
+ * android.hardware.location.ContextHubManager#findEndpoints} or register updates through
+ * {@link android.hardware.location.ContextHubManager#registerEndpointDiscoveryCallback}
+ * to get notified if the endpoint restarts.
+ */
+ public static final int REASON_ENDPOINT_STOPPED = 6;
+
private final Object mLock = new Object();
private final HubEndpointInfo mPendingHubEndpointInfo;
@Nullable private final IHubEndpointLifecycleCallback mLifecycleCallback;
@@ -173,9 +216,7 @@ public class HubEndpoint {
try {
mServiceToken.closeSession(
- sessionId,
- IHubEndpointLifecycleCallback
- .REASON_OPEN_ENDPOINT_SESSION_REQUEST_REJECTED);
+ sessionId, REASON_OPEN_ENDPOINT_SESSION_REQUEST_REJECTED);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
@@ -396,9 +437,7 @@ public class HubEndpoint {
try {
// Oneway notification to system service
- serviceToken.closeSession(
- session.getId(),
- IHubEndpointLifecycleCallback.REASON_CLOSE_ENDPOINT_SESSION_REQUESTED);
+ serviceToken.closeSession(session.getId(), REASON_CLOSE_ENDPOINT_SESSION_REQUESTED);
} catch (RemoteException e) {
Log.e(TAG, "closeSession: failed to close session " + session, e);
e.rethrowFromSystemServer();
diff --git a/core/java/android/hardware/contexthub/IContextHubEndpointDiscoveryCallback.aidl b/core/java/android/hardware/contexthub/IContextHubEndpointDiscoveryCallback.aidl
new file mode 100644
index 000000000000..245be930a897
--- /dev/null
+++ b/core/java/android/hardware/contexthub/IContextHubEndpointDiscoveryCallback.aidl
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.contexthub;
+
+import android.hardware.contexthub.HubEndpointInfo;
+
+/**
+ * @hide
+ */
+oneway interface IContextHubEndpointDiscoveryCallback {
+ /**
+ * Called when endpoint(s) start.
+ * @param hubEndpointInfoList The list of endpoints that started.
+ */
+ void onEndpointsStarted(in HubEndpointInfo[] hubEndpointInfoList);
+
+ /**
+ * Called when endpoint(s) stopped.
+ * @param hubEndpointInfoList The list of endpoints that started.
+ * @param reason The reason why the endpoints stopped.
+ */
+ void onEndpointsStopped(in HubEndpointInfo[] hubEndpointInfoList, int reason);
+}
diff --git a/core/java/android/hardware/contexthub/IHubEndpointDiscoveryCallback.java b/core/java/android/hardware/contexthub/IHubEndpointDiscoveryCallback.java
new file mode 100644
index 000000000000..a61a7ebd0de9
--- /dev/null
+++ b/core/java/android/hardware/contexthub/IHubEndpointDiscoveryCallback.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.contexthub;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.chre.flags.Flags;
+
+import java.util.List;
+
+/**
+ * Interface for listening to updates about endpoint availability.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_OFFLOAD_API)
+public interface IHubEndpointDiscoveryCallback {
+ /**
+ * Called when a list of hub endpoints have started.
+ *
+ * @param discoveryInfoList The list containing hub discovery information.
+ */
+ void onEndpointsStarted(@NonNull List<HubDiscoveryInfo> discoveryInfoList);
+
+ /**
+ * Called when a list of hub endpoints have stopped.
+ *
+ * @param discoveryInfoList The list containing hub discovery information.
+ * @param reason The reason the endpoints stopped.
+ */
+ void onEndpointsStopped(
+ @NonNull List<HubDiscoveryInfo> discoveryInfoList, @HubEndpoint.Reason int reason);
+}
diff --git a/core/java/android/hardware/contexthub/IHubEndpointLifecycleCallback.java b/core/java/android/hardware/contexthub/IHubEndpointLifecycleCallback.java
index 46884393b49b..fe449bb5ce0e 100644
--- a/core/java/android/hardware/contexthub/IHubEndpointLifecycleCallback.java
+++ b/core/java/android/hardware/contexthub/IHubEndpointLifecycleCallback.java
@@ -17,15 +17,11 @@
package android.hardware.contexthub;
import android.annotation.FlaggedApi;
-import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.chre.flags.Flags;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
/**
* Interface for listening to lifecycle events of a hub endpoint.
*
@@ -34,24 +30,6 @@ import java.lang.annotation.RetentionPolicy;
@SystemApi
@FlaggedApi(Flags.FLAG_OFFLOAD_API)
public interface IHubEndpointLifecycleCallback {
- /** Unknown reason. */
- int REASON_UNSPECIFIED = 0;
-
- /** The peer rejected the request to open this endpoint session. */
- int REASON_OPEN_ENDPOINT_SESSION_REQUEST_REJECTED = 3;
-
- /** The peer closed this endpoint session. */
- int REASON_CLOSE_ENDPOINT_SESSION_REQUESTED = 4;
-
- /** @hide */
- @Retention(RetentionPolicy.SOURCE)
- @IntDef({
- REASON_UNSPECIFIED,
- REASON_OPEN_ENDPOINT_SESSION_REQUEST_REJECTED,
- REASON_CLOSE_ENDPOINT_SESSION_REQUESTED,
- })
- @interface EndpointLifecycleReason {}
-
/**
* Called when an endpoint is requesting a session be opened with another endpoint.
*
@@ -78,5 +56,5 @@ public interface IHubEndpointLifecycleCallback {
* used.
* @param reason The reason why this session was closed.
*/
- void onSessionClosed(@NonNull HubEndpointSession session, @EndpointLifecycleReason int reason);
+ void onSessionClosed(@NonNull HubEndpointSession session, @HubEndpoint.Reason int reason);
}
diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java
index 426cd69f76a0..117d8fe24809 100644
--- a/core/java/android/hardware/location/ContextHubManager.java
+++ b/core/java/android/hardware/location/ContextHubManager.java
@@ -38,6 +38,8 @@ import android.hardware.contexthub.HubDiscoveryInfo;
import android.hardware.contexthub.HubEndpoint;
import android.hardware.contexthub.HubEndpointInfo;
import android.hardware.contexthub.HubServiceInfo;
+import android.hardware.contexthub.IContextHubEndpointDiscoveryCallback;
+import android.hardware.contexthub.IHubEndpointDiscoveryCallback;
import android.hardware.contexthub.IHubEndpointLifecycleCallback;
import android.os.Handler;
import android.os.HandlerExecutor;
@@ -49,7 +51,9 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
/**
@@ -202,6 +206,10 @@ public final class ContextHubManager {
private Callback mCallback;
private Handler mCallbackHandler;
+ /** A map of endpoint discovery callbacks currently registered */
+ private Map<IHubEndpointDiscoveryCallback, IContextHubEndpointDiscoveryCallback>
+ mDiscoveryCallbacks = new ConcurrentHashMap<>();
+
/**
* @deprecated Use {@code mCallback} instead.
*/
@@ -694,8 +702,6 @@ public final class ContextHubManager {
@RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
@NonNull
public List<HubDiscoveryInfo> findEndpoints(long endpointId) {
- // TODO(b/379323274): Consider improving these getters to avoid racing with nano app load
- // timing.
try {
List<HubEndpointInfo> endpointInfos = mService.findEndpoints(endpointId);
List<HubDiscoveryInfo> results = new ArrayList<>(endpointInfos.size());
@@ -720,8 +726,6 @@ public final class ContextHubManager {
@RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
@NonNull
public List<HubDiscoveryInfo> findEndpoints(@NonNull String serviceDescriptor) {
- // TODO(b/379323274): Consider improving these getters to avoid racing with nano app load
- // timing.
if (serviceDescriptor.isBlank()) {
throw new IllegalArgumentException("Invalid service descriptor: " + serviceDescriptor);
}
@@ -744,6 +748,188 @@ public final class ContextHubManager {
}
/**
+ * Creates an interface to invoke endpoint discovery callbacks to send down to the service.
+ *
+ * @param callback the callback to invoke at the client process
+ * @param executor the executor to invoke callbacks for this client
+ * @return the callback interface
+ */
+ private IContextHubEndpointDiscoveryCallback createDiscoveryCallback(
+ IHubEndpointDiscoveryCallback callback,
+ Executor executor,
+ @Nullable String serviceDescriptor) {
+ return new IContextHubEndpointDiscoveryCallback.Stub() {
+ @Override
+ public void onEndpointsStarted(HubEndpointInfo[] hubEndpointInfoList) {
+ if (hubEndpointInfoList.length == 0) {
+ Log.w(TAG, "onEndpointsStarted: received empty discovery list");
+ return;
+ }
+ executor.execute(
+ () -> {
+ // TODO(b/380293951): Refactor
+ List<HubDiscoveryInfo> discoveryList =
+ new ArrayList<>(hubEndpointInfoList.length);
+ for (HubEndpointInfo info : hubEndpointInfoList) {
+ if (serviceDescriptor != null) {
+ for (HubServiceInfo sInfo : info.getServiceInfoCollection()) {
+ if (sInfo.getServiceDescriptor()
+ .equals(serviceDescriptor)) {
+ discoveryList.add(new HubDiscoveryInfo(info, sInfo));
+ }
+ }
+ } else {
+ discoveryList.add(new HubDiscoveryInfo(info));
+ }
+ }
+ if (discoveryList.isEmpty()) {
+ Log.w(TAG, "onEndpointsStarted: no matching service descriptor");
+ } else {
+ callback.onEndpointsStarted(discoveryList);
+ }
+ });
+ }
+
+ @Override
+ public void onEndpointsStopped(HubEndpointInfo[] hubEndpointInfoList, int reason) {
+ if (hubEndpointInfoList.length == 0) {
+ Log.w(TAG, "onEndpointsStopped: received empty discovery list");
+ return;
+ }
+ executor.execute(
+ () -> {
+ List<HubDiscoveryInfo> discoveryList =
+ new ArrayList<>(hubEndpointInfoList.length);
+ for (HubEndpointInfo info : hubEndpointInfoList) {
+ if (serviceDescriptor != null) {
+ for (HubServiceInfo sInfo : info.getServiceInfoCollection()) {
+ if (sInfo.getServiceDescriptor()
+ .equals(serviceDescriptor)) {
+ discoveryList.add(new HubDiscoveryInfo(info, sInfo));
+ }
+ }
+ } else {
+ discoveryList.add(new HubDiscoveryInfo(info));
+ }
+ }
+ if (discoveryList.isEmpty()) {
+ Log.w(TAG, "onEndpointsStopped: no matching service descriptor");
+ } else {
+ callback.onEndpointsStopped(discoveryList, reason);
+ }
+ });
+ }
+ };
+ }
+
+ /**
+ * Equivalent to {@link #registerEndpointDiscoveryCallback(long, IHubEndpointDiscoveryCallback,
+ * Executor)} with the default executor in the main thread.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
+ @FlaggedApi(Flags.FLAG_OFFLOAD_API)
+ public void registerEndpointDiscoveryCallback(
+ long endpointId, @NonNull IHubEndpointDiscoveryCallback callback) {
+ registerEndpointDiscoveryCallback(
+ endpointId, callback, new HandlerExecutor(Handler.getMain()));
+ }
+
+ /**
+ * Registers a callback to be notified when the hub endpoint with the corresponding endpoint ID
+ * has started or stopped.
+ *
+ * @param endpointId The identifier of the hub endpoint.
+ * @param callback The callback to be invoked.
+ * @param executor The executor to invoke the callback on.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
+ @FlaggedApi(Flags.FLAG_OFFLOAD_API)
+ public void registerEndpointDiscoveryCallback(
+ long endpointId,
+ @NonNull IHubEndpointDiscoveryCallback callback,
+ @NonNull Executor executor) {
+ Objects.requireNonNull(callback, "callback cannot be null");
+ Objects.requireNonNull(executor, "executor cannot be null");
+ IContextHubEndpointDiscoveryCallback iCallback =
+ createDiscoveryCallback(callback, executor, null);
+ try {
+ mService.registerEndpointDiscoveryCallbackId(endpointId, iCallback);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+
+ mDiscoveryCallbacks.put(callback, iCallback);
+ }
+
+ /**
+ * Equivalent to {@link #registerEndpointDiscoveryCallback(String,
+ * IHubEndpointDiscoveryCallback, Executor)} with the default executor in the main thread.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
+ @FlaggedApi(Flags.FLAG_OFFLOAD_API)
+ public void registerEndpointDiscoveryCallback(
+ @NonNull String serviceDescriptor, @NonNull IHubEndpointDiscoveryCallback callback) {
+ registerEndpointDiscoveryCallback(
+ serviceDescriptor, callback, new HandlerExecutor(Handler.getMain()));
+ }
+
+ /**
+ * Registers a callback to be notified when the hub endpoint with the corresponding service
+ * descriptor has started or stopped.
+ *
+ * @param serviceDescriptor The service descriptor of the hub endpoint.
+ * @param callback The callback to be invoked.
+ * @param executor The executor to invoke the callback on.
+ * @throws IllegalArgumentException if the serviceDescriptor is empty.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
+ @FlaggedApi(Flags.FLAG_OFFLOAD_API)
+ public void registerEndpointDiscoveryCallback(
+ @NonNull String serviceDescriptor,
+ @NonNull IHubEndpointDiscoveryCallback callback,
+ @NonNull Executor executor) {
+ Objects.requireNonNull(serviceDescriptor, "serviceDescriptor cannot be null");
+ Objects.requireNonNull(callback, "callback cannot be null");
+ Objects.requireNonNull(executor, "executor cannot be null");
+ if (serviceDescriptor.isBlank()) {
+ throw new IllegalArgumentException("Invalid service descriptor: " + serviceDescriptor);
+ }
+
+ IContextHubEndpointDiscoveryCallback iCallback =
+ createDiscoveryCallback(callback, executor, serviceDescriptor);
+ try {
+ mService.registerEndpointDiscoveryCallbackDescriptor(serviceDescriptor, iCallback);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+
+ mDiscoveryCallbacks.put(callback, iCallback);
+ }
+
+ /**
+ * Unregisters a previously registered endpoint discovery callback.
+ *
+ * @param callback The callback previously registered.
+ * @throws IllegalArgumentException If the callback was not previously registered.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
+ @FlaggedApi(Flags.FLAG_OFFLOAD_API)
+ public void unregisterEndpointDiscoveryCallback(
+ @NonNull IHubEndpointDiscoveryCallback callback) {
+ Objects.requireNonNull(callback, "callback cannot be null");
+ IContextHubEndpointDiscoveryCallback iCallback = mDiscoveryCallbacks.remove(callback);
+ if (iCallback == null) {
+ throw new IllegalArgumentException("Callback not previously registered");
+ }
+
+ try {
+ mService.unregisterEndpointDiscoveryCallback(iCallback);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Set a callback to receive messages from the context hub
*
* @param callback Callback object
diff --git a/core/java/android/hardware/location/IContextHubService.aidl b/core/java/android/hardware/location/IContextHubService.aidl
index f9f412446038..f14aadcab474 100644
--- a/core/java/android/hardware/location/IContextHubService.aidl
+++ b/core/java/android/hardware/location/IContextHubService.aidl
@@ -21,6 +21,7 @@ import android.app.PendingIntent;
import android.hardware.contexthub.HubEndpointInfo;
import android.hardware.contexthub.IContextHubEndpoint;
import android.hardware.contexthub.IContextHubEndpointCallback;
+import android.hardware.contexthub.IContextHubEndpointDiscoveryCallback;
import android.hardware.location.ContextHubInfo;
import android.hardware.location.ContextHubMessage;
import android.hardware.location.HubInfo;
@@ -137,4 +138,16 @@ interface IContextHubService {
// Register an endpoint with the context hub
@EnforcePermission("ACCESS_CONTEXT_HUB")
IContextHubEndpoint registerEndpoint(in HubEndpointInfo pendingEndpointInfo, in IContextHubEndpointCallback callback);
+
+ // Register an endpoint discovery callback (id)
+ @EnforcePermission("ACCESS_CONTEXT_HUB")
+ void registerEndpointDiscoveryCallbackId(long endpointId, in IContextHubEndpointDiscoveryCallback callback);
+
+ // Register an endpoint discovery callback (descriptor)
+ @EnforcePermission("ACCESS_CONTEXT_HUB")
+ void registerEndpointDiscoveryCallbackDescriptor(String serviceDescriptor, in IContextHubEndpointDiscoveryCallback callback);
+
+ // Unregister an endpoint with the context hub
+ @EnforcePermission("ACCESS_CONTEXT_HUB")
+ void unregisterEndpointDiscoveryCallback(in IContextHubEndpointDiscoveryCallback callback);
}
diff --git a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
index 3219ce81c256..b270062cbffc 100644
--- a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
+++ b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java
@@ -789,11 +789,17 @@ public final class VcnGatewayConnectionConfig {
public Builder setMinUdpPort4500NatTimeoutSeconds(
@IntRange(from = MIN_UDP_PORT_4500_NAT_TIMEOUT_SECONDS)
int minUdpPort4500NatTimeoutSeconds) {
- Preconditions.checkArgument(
- minUdpPort4500NatTimeoutSeconds == MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET
- || minUdpPort4500NatTimeoutSeconds
- >= MIN_UDP_PORT_4500_NAT_TIMEOUT_SECONDS,
- "Timeout must be at least 120s or MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET");
+ if (Flags.mainlineVcnModuleApi()) {
+ Preconditions.checkArgument(
+ minUdpPort4500NatTimeoutSeconds == MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET
+ || minUdpPort4500NatTimeoutSeconds
+ >= MIN_UDP_PORT_4500_NAT_TIMEOUT_SECONDS,
+ "Timeout must be at least 120s or MIN_UDP_PORT_4500_NAT_TIMEOUT_UNSET");
+ } else {
+ Preconditions.checkArgument(
+ minUdpPort4500NatTimeoutSeconds >= MIN_UDP_PORT_4500_NAT_TIMEOUT_SECONDS,
+ "Timeout must be at least 120s");
+ }
mMinUdpPort4500NatTimeoutSeconds = minUdpPort4500NatTimeoutSeconds;
return this;
diff --git a/core/java/android/os/AggregateBatteryConsumer.java b/core/java/android/os/AggregateBatteryConsumer.java
index f0e12ca644dd..f5fee4f2d9be 100644
--- a/core/java/android/os/AggregateBatteryConsumer.java
+++ b/core/java/android/os/AggregateBatteryConsumer.java
@@ -17,7 +17,6 @@
package android.os;
import android.annotation.NonNull;
-import android.util.proto.ProtoOutputStream;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
@@ -100,19 +99,6 @@ public final class AggregateBatteryConsumer extends BatteryConsumer {
}
}
- void writePowerComponentModelProto(@NonNull ProtoOutputStream proto) {
- for (int i = 0; i < POWER_COMPONENT_COUNT; i++) {
- final int powerModel = getPowerModel(i);
- if (powerModel == BatteryConsumer.POWER_MODEL_UNDEFINED) continue;
-
- final long token = proto.start(BatteryUsageStatsAtomsProto.COMPONENT_MODELS);
- proto.write(BatteryUsageStatsAtomsProto.PowerComponentModel.COMPONENT, i);
- proto.write(BatteryUsageStatsAtomsProto.PowerComponentModel.POWER_MODEL,
- powerModelToProtoEnum(powerModel));
- proto.end(token);
- }
- }
-
/**
* Builder for DeviceBatteryConsumer.
*/
diff --git a/core/java/android/os/BatteryConsumer.java b/core/java/android/os/BatteryConsumer.java
index 14b67f64b6da..96ea1683b5cf 100644
--- a/core/java/android/os/BatteryConsumer.java
+++ b/core/java/android/os/BatteryConsumer.java
@@ -161,18 +161,27 @@ public abstract class BatteryConsumer {
/**
* Unspecified power model.
+ *
+ * @deprecated PowerModel is no longer supported
*/
+ @Deprecated
public static final int POWER_MODEL_UNDEFINED = 0;
/**
* Power model that is based on average consumption rates that hardware components
* consume in various states.
+ *
+ * @deprecated PowerModel is no longer supported
*/
+ @Deprecated
public static final int POWER_MODEL_POWER_PROFILE = 1;
/**
* Power model that is based on energy consumption stats provided by PowerStats HAL.
+ *
+ * @deprecated PowerModel is no longer supported
*/
+ @Deprecated
public static final int POWER_MODEL_ENERGY_CONSUMPTION = 2;
/**
@@ -380,19 +389,17 @@ public abstract class BatteryConsumer {
public final @ScreenState int screenState;
public final @PowerState int powerState;
- final int mPowerModelColumnIndex;
final int mPowerColumnIndex;
final int mDurationColumnIndex;
private Key(@PowerComponentId int powerComponentId, @ProcessState int processState,
- @ScreenState int screenState, @PowerState int powerState, int powerModelColumnIndex,
+ @ScreenState int screenState, @PowerState int powerState,
int powerColumnIndex, int durationColumnIndex) {
this.powerComponentId = powerComponentId;
this.processState = processState;
this.screenState = screenState;
this.powerState = powerState;
- mPowerModelColumnIndex = powerModelColumnIndex;
mPowerColumnIndex = powerColumnIndex;
mDurationColumnIndex = durationColumnIndex;
}
@@ -577,11 +584,11 @@ public abstract class BatteryConsumer {
*
* @param componentId The ID of the power component, e.g.
* {@link BatteryConsumer#POWER_COMPONENT_CPU}.
+ * @deprecated PowerModel is no longer supported
*/
+ @Deprecated
public @PowerModel int getPowerModel(@PowerComponentId int componentId) {
- return mPowerComponents.getPowerModel(
- mData.layout.getKeyOrThrow(componentId, PROCESS_STATE_UNSPECIFIED,
- SCREEN_STATE_UNSPECIFIED, POWER_STATE_UNSPECIFIED));
+ return POWER_MODEL_UNDEFINED;
}
/**
@@ -589,9 +596,11 @@ public abstract class BatteryConsumer {
*
* @param key The key of the power component, obtained by calling {@link #getKey} or
* {@link #getKeys} method.
+ * @deprecated PowerModel is no longer supported
*/
+ @Deprecated
public @PowerModel int getPowerModel(@NonNull BatteryConsumer.Key key) {
- return mPowerComponents.getPowerModel(key);
+ return POWER_MODEL_UNDEFINED;
}
/**
@@ -657,20 +666,6 @@ public abstract class BatteryConsumer {
}
/**
- * Returns the name of the specified power model. Intended for logging and debugging.
- */
- public static String powerModelToString(@BatteryConsumer.PowerModel int powerModel) {
- switch (powerModel) {
- case BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION:
- return "energy consumption";
- case BatteryConsumer.POWER_MODEL_POWER_PROFILE:
- return "power profile";
- default:
- return "";
- }
- }
-
- /**
* Returns the equivalent PowerModel enum for the specified power model.
* {@see BatteryUsageStatsAtomsProto.BatteryConsumerData.PowerComponentUsage.PowerModel}
*/
@@ -857,10 +852,8 @@ public abstract class BatteryConsumer {
static class BatteryConsumerDataLayout {
private static final Key[] KEY_ARRAY = new Key[0];
- public static final int POWER_MODEL_NOT_INCLUDED = -1;
public final String[] customPowerComponentNames;
public final int customPowerComponentCount;
- public final boolean powerModelsIncluded;
public final boolean processStateDataIncluded;
public final boolean screenStateDataIncluded;
public final boolean powerStateDataIncluded;
@@ -872,11 +865,10 @@ public abstract class BatteryConsumer {
private SparseArray<Key[]> mPerComponentKeys;
private BatteryConsumerDataLayout(int firstColumn, String[] customPowerComponentNames,
- boolean powerModelsIncluded, boolean includeProcessStateData,
- boolean includeScreenState, boolean includePowerState) {
+ boolean includeProcessStateData, boolean includeScreenState,
+ boolean includePowerState) {
this.customPowerComponentNames = customPowerComponentNames;
this.customPowerComponentCount = customPowerComponentNames.length;
- this.powerModelsIncluded = powerModelsIncluded;
this.processStateDataIncluded = includeProcessStateData;
this.screenStateDataIncluded = includeScreenState;
this.powerStateDataIncluded = includePowerState;
@@ -904,7 +896,7 @@ public abstract class BatteryConsumer {
continue;
}
for (int i = 0; i < powerComponentIds.length; i++) {
- columnIndex = addKeys(keyList, powerModelsIncluded, includeProcessStateData,
+ columnIndex = addKeys(keyList, includeProcessStateData,
powerComponentIds[i], screenState, powerState, columnIndex);
}
}
@@ -934,13 +926,10 @@ public abstract class BatteryConsumer {
}
}
- private int addKeys(List<Key> keys, boolean powerModelsIncluded,
- boolean includeProcessStateData, @PowerComponentId int componentId,
- int screenState, int powerState, int columnIndex) {
+ private int addKeys(List<Key> keys, boolean includeProcessStateData,
+ @PowerComponentId int componentId, int screenState, int powerState,
+ int columnIndex) {
keys.add(new Key(componentId, PROCESS_STATE_UNSPECIFIED, screenState, powerState,
- powerModelsIncluded
- ? columnIndex++
- : POWER_MODEL_NOT_INCLUDED, // power model
columnIndex++, // power
columnIndex++ // usage duration
));
@@ -956,9 +945,6 @@ public abstract class BatteryConsumer {
continue;
}
keys.add(new Key(componentId, processState, screenState, powerState,
- powerModelsIncluded
- ? columnIndex++
- : POWER_MODEL_NOT_INCLUDED, // power model
columnIndex++, // power
columnIndex++ // usage duration
));
@@ -1016,7 +1002,7 @@ public abstract class BatteryConsumer {
}
static BatteryConsumerDataLayout createBatteryConsumerDataLayout(
- String[] customPowerComponentNames, boolean includePowerModels,
+ String[] customPowerComponentNames,
boolean includeProcessStateData, boolean includeScreenStateData,
boolean includePowerStateData) {
int columnCount = BatteryConsumer.COLUMN_COUNT;
@@ -1025,8 +1011,7 @@ public abstract class BatteryConsumer {
columnCount = Math.max(columnCount, UserBatteryConsumer.COLUMN_COUNT);
return new BatteryConsumerDataLayout(columnCount, customPowerComponentNames,
- includePowerModels, includeProcessStateData, includeScreenStateData,
- includePowerStateData);
+ includeProcessStateData, includeScreenStateData, includePowerStateData);
}
protected abstract static class BaseBuilder<T extends BaseBuilder<?>> {
@@ -1086,7 +1071,7 @@ public abstract class BatteryConsumer {
public T setConsumedPower(@PowerComponentId int componentId, double componentPower,
@PowerModel int powerModel) {
mPowerComponentsBuilder.setConsumedPower(getKey(componentId, PROCESS_STATE_UNSPECIFIED),
- componentPower, powerModel);
+ componentPower);
return (T) this;
}
@@ -1095,14 +1080,14 @@ public abstract class BatteryConsumer {
public T addConsumedPower(@PowerComponentId int componentId, double componentPower,
@PowerModel int powerModel) {
mPowerComponentsBuilder.addConsumedPower(getKey(componentId, PROCESS_STATE_UNSPECIFIED),
- componentPower, powerModel);
+ componentPower);
return (T) this;
}
@SuppressWarnings("unchecked")
@NonNull
public T setConsumedPower(Key key, double componentPower, @PowerModel int powerModel) {
- mPowerComponentsBuilder.setConsumedPower(key, componentPower, powerModel);
+ mPowerComponentsBuilder.setConsumedPower(key, componentPower);
return (T) this;
}
@@ -1110,21 +1095,14 @@ public abstract class BatteryConsumer {
@NonNull
public T addConsumedPower(@PowerComponentId int componentId, double componentPower) {
mPowerComponentsBuilder.addConsumedPower(getKey(componentId, PROCESS_STATE_UNSPECIFIED),
- componentPower, POWER_MODEL_UNDEFINED);
+ componentPower);
return (T) this;
}
@SuppressWarnings("unchecked")
@NonNull
public T addConsumedPower(Key key, double componentPower) {
- mPowerComponentsBuilder.addConsumedPower(key, componentPower, POWER_MODEL_UNDEFINED);
- return (T) this;
- }
-
- @SuppressWarnings("unchecked")
- @NonNull
- public T addConsumedPower(Key key, double componentPower, @PowerModel int powerModel) {
- mPowerComponentsBuilder.addConsumedPower(key, componentPower, powerModel);
+ mPowerComponentsBuilder.addConsumedPower(key, componentPower);
return (T) this;
}
diff --git a/core/java/android/os/BatteryManager.java b/core/java/android/os/BatteryManager.java
index 8b267bf28c7e..b63ad5f0148e 100644
--- a/core/java/android/os/BatteryManager.java
+++ b/core/java/android/os/BatteryManager.java
@@ -167,76 +167,90 @@ public class BatteryManager {
public static final String EXTRA_CHARGING_STATUS = "android.os.extra.CHARGING_STATUS";
/**
- * Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
- * Int value representing the battery's capacity level. These constants are key indicators of
- * battery status and system capabilities, guiding power management decisions for both the
- * system and apps:
- * {@link #BATTERY_CAPACITY_LEVEL_UNSUPPORTED}: Feature not supported on this device.
- * {@link #BATTERY_CAPACITY_LEVEL_UNKNOWN}: Battery status is unavailable or uninitialized.
- * {@link #BATTERY_CAPACITY_LEVEL_CRITICAL}: Battery is critically low and the Android
- * framework has been notified to schedule a shutdown by this value
- * {@link #BATTERY_CAPACITY_LEVEL_LOW}: Android framework must limit background jobs to
- * avoid impacting charging speed
- * {@link #BATTERY_CAPACITY_LEVEL_NORMAL}: Battery level and charging rates are normal,
- * battery temperature is within normal range and adapter power is enough to charge the
- * battery at an acceptable rate. Android framework can run light background tasks without
- * affecting charging performance severely.
- * {@link #BATTERY_CAPACITY_LEVEL_HIGH}: Battery level is high, battery temperature is
- * within normal range and adapter power is enough to charge the battery at an acceptable
- * rate while running background loads. Android framework can run background tasks without
- * affecting charging or battery performance.
- * {@link #BATTERY_CAPACITY_LEVEL_FULL}: The battery is full, battery temperature is
- * within normal range and adapter power is enough to sustain running background loads.
- * Android framework can run background tasks without affecting the battery level or
- * battery performance.
- */
-
- @FlaggedApi(FLAG_BATTERY_PART_STATUS_API)
- public static final String EXTRA_CAPACITY_LEVEL = "android.os.extra.CAPACITY_LEVEL";
-
- /**
- * Battery capacity level is unsupported. @see EXTRA_CAPACITY_LEVEL
+ * Battery capacity level is unsupported.
+ *
+ * @see #EXTRA_CAPACITY_LEVEL
*/
@FlaggedApi(FLAG_BATTERY_PART_STATUS_API)
public static final int BATTERY_CAPACITY_LEVEL_UNSUPPORTED = -1;
/**
- * Battery capacity level is unknown. @see EXTRA_CAPACITY_LEVEL
+ * Battery capacity level is unknown.
+ *
+ * @see #EXTRA_CAPACITY_LEVEL
*/
@FlaggedApi(FLAG_BATTERY_PART_STATUS_API)
public static final int BATTERY_CAPACITY_LEVEL_UNKNOWN = 0;
/**
- * Battery capacity level is critical. @see EXTRA_CAPACITY_LEVEL
+ * Battery capacity level is critical. The Android framework has been notified to schedule
+ * a shutdown by this value.
+ *
+ * @see #EXTRA_CAPACITY_LEVEL
*/
@FlaggedApi(FLAG_BATTERY_PART_STATUS_API)
public static final int BATTERY_CAPACITY_LEVEL_CRITICAL = 1;
/**
- * Battery capacity level is low. @see EXTRA_CAPACITY_LEVEL
+ * Battery capacity level is low. The Android framework must limit background jobs to avoid
+ * impacting charging speed.
+ *
+ * @see #EXTRA_CAPACITY_LEVEL
*/
@FlaggedApi(FLAG_BATTERY_PART_STATUS_API)
public static final int BATTERY_CAPACITY_LEVEL_LOW = 2;
/**
- * Battery capacity level is normal. @see EXTRA_CAPACITY_LEVEL
+ * Battery capacity level is normal. Battery level and charging rates are normal, battery
+ * temperature is within the normal range, and adapter power is enough to charge the battery
+ * at an acceptable rate. The Android framework can run light background tasks without
+ * affecting charging performance severely.
+ *
+ * @see #EXTRA_CAPACITY_LEVEL
*/
@FlaggedApi(FLAG_BATTERY_PART_STATUS_API)
public static final int BATTERY_CAPACITY_LEVEL_NORMAL = 3;
/**
- * Battery capacity level is high. @see EXTRA_CAPACITY_LEVEL
+ * Battery capacity level is high. Battery level is high, battery temperature is within the
+ * normal range, and adapter power is enough to charge the battery at an acceptable rate
+ * while running background loads. The Android framework can run background tasks without
+ * affecting charging or battery performance.
+ *
+ * @see #EXTRA_CAPACITY_LEVEL
*/
@FlaggedApi(FLAG_BATTERY_PART_STATUS_API)
public static final int BATTERY_CAPACITY_LEVEL_HIGH = 4;
/**
- * Battery capacity level is full. @see EXTRA_CAPACITY_LEVEL
+ * Battery capacity level is full. The battery is full, the battery temperature is within the
+ * normal range, and adapter power is enough to sustain running background loads. The Android
+ * framework can run background tasks without affecting the battery level or battery
+ * performance.
+ *
+ * @see #EXTRA_CAPACITY_LEVEL
*/
@FlaggedApi(FLAG_BATTERY_PART_STATUS_API)
public static final int BATTERY_CAPACITY_LEVEL_FULL = 5;
/**
+ * Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
+ * Int value representing the battery's capacity level. These constants are key indicators of
+ * battery status and system capabilities, guiding power management decisions for both the
+ * system and apps.
+ *
+ * @see #BATTERY_CAPACITY_LEVEL_UNSUPPORTED
+ * @see #BATTERY_CAPACITY_LEVEL_UNKNOWN
+ * @see #BATTERY_CAPACITY_LEVEL_CRITICAL
+ * @see #BATTERY_CAPACITY_LEVEL_LOW
+ * @see #BATTERY_CAPACITY_LEVEL_NORMAL
+ * @see #BATTERY_CAPACITY_LEVEL_HIGH
+ * @see #BATTERY_CAPACITY_LEVEL_FULL
+ */
+ @FlaggedApi(FLAG_BATTERY_PART_STATUS_API)
+ public static final String EXTRA_CAPACITY_LEVEL = "android.os.extra.CAPACITY_LEVEL";
+
+ /**
* Extra for {@link android.content.Intent#ACTION_BATTERY_LEVEL_CHANGED}:
* Contains list of Bundles representing battery events
* @hide
diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java
index 72e4cef2f6eb..f913fcfd56d4 100644
--- a/core/java/android/os/BatteryUsageStats.java
+++ b/core/java/android/os/BatteryUsageStats.java
@@ -114,7 +114,6 @@ public final class BatteryUsageStats implements Parcelable, Closeable {
static final String XML_ATTR_POWER_STATE = "power_state";
static final String XML_ATTR_POWER = "power";
static final String XML_ATTR_DURATION = "duration";
- static final String XML_ATTR_MODEL = "model";
static final String XML_ATTR_BATTERY_CAPACITY = "battery_capacity";
static final String XML_ATTR_DISCHARGE_PERCENT = "discharge_pct";
static final String XML_ATTR_DISCHARGE_LOWER = "discharge_lower";
@@ -155,7 +154,6 @@ public final class BatteryUsageStats implements Parcelable, Closeable {
private final long mBatteryTimeRemainingMs;
private final long mChargeTimeRemainingMs;
private final String[] mCustomPowerComponentNames;
- private final boolean mIncludesPowerModels;
private final boolean mIncludesProcessStateData;
private final boolean mIncludesScreenStateData;
private final boolean mIncludesPowerStateData;
@@ -179,7 +177,6 @@ public final class BatteryUsageStats implements Parcelable, Closeable {
mBatteryTimeRemainingMs = builder.mBatteryTimeRemainingMs;
mChargeTimeRemainingMs = builder.mChargeTimeRemainingMs;
mCustomPowerComponentNames = builder.mCustomPowerComponentNames;
- mIncludesPowerModels = builder.mIncludePowerModels;
mIncludesProcessStateData = builder.mIncludesProcessStateData;
mIncludesScreenStateData = builder.mIncludesScreenStateData;
mIncludesPowerStateData = builder.mIncludesPowerStateData;
@@ -364,14 +361,13 @@ public final class BatteryUsageStats implements Parcelable, Closeable {
mBatteryTimeRemainingMs = source.readLong();
mChargeTimeRemainingMs = source.readLong();
mCustomPowerComponentNames = source.readStringArray();
- mIncludesPowerModels = source.readBoolean();
mIncludesProcessStateData = source.readBoolean();
mIncludesScreenStateData = source.readBoolean();
mIncludesPowerStateData = source.readBoolean();
mBatteryConsumersCursorWindow = CursorWindow.newFromParcel(source);
mBatteryConsumerDataLayout = BatteryConsumer.createBatteryConsumerDataLayout(
- mCustomPowerComponentNames, mIncludesPowerModels, mIncludesProcessStateData,
+ mCustomPowerComponentNames, mIncludesProcessStateData,
mIncludesScreenStateData, mIncludesPowerStateData);
final int numRows = mBatteryConsumersCursorWindow.getNumRows();
@@ -424,7 +420,6 @@ public final class BatteryUsageStats implements Parcelable, Closeable {
dest.writeLong(mBatteryTimeRemainingMs);
dest.writeLong(mChargeTimeRemainingMs);
dest.writeStringArray(mCustomPowerComponentNames);
- dest.writeBoolean(mIncludesPowerModels);
dest.writeBoolean(mIncludesProcessStateData);
dest.writeBoolean(mIncludesScreenStateData);
dest.writeBoolean(mIncludesPowerStateData);
@@ -506,9 +501,6 @@ public final class BatteryUsageStats implements Parcelable, Closeable {
getDischargeDurationMs());
deviceBatteryConsumer.writeStatsProto(proto,
BatteryUsageStatsAtomsProto.DEVICE_BATTERY_CONSUMER);
- if (mIncludesPowerModels) {
- deviceBatteryConsumer.writePowerComponentModelProto(proto);
- }
writeUidBatteryConsumersProto(proto, maxRawSize);
}
@@ -629,7 +621,7 @@ public final class BatteryUsageStats implements Parcelable, Closeable {
printPowerComponent(pw, prefix,
mBatteryConsumerDataLayout.getPowerComponentName(powerComponent),
- devicePowerMah, appsPowerMah, BatteryConsumer.POWER_MODEL_UNDEFINED,
+ devicePowerMah, appsPowerMah,
deviceConsumer.getUsageDurationMillis(powerComponent));
}
@@ -716,23 +708,15 @@ public final class BatteryUsageStats implements Parcelable, Closeable {
printPowerComponent(pw, prefix,
mBatteryConsumerDataLayout.getPowerComponentName(powerComponent),
devicePowerMah, appsPowerMah,
- mIncludesPowerModels ? deviceConsumer.getPowerModel(powerComponent)
- : BatteryConsumer.POWER_MODEL_UNDEFINED,
deviceConsumer.getUsageDurationMillis(dimensions));
}
}
private void printPowerComponent(PrintWriter pw, String prefix, String label,
- double devicePowerMah, double appsPowerMah, int powerModel, long durationMs) {
+ double devicePowerMah, double appsPowerMah, long durationMs) {
StringBuilder sb = new StringBuilder();
sb.append(prefix).append(" ").append(label).append(": ")
.append(BatteryStats.formatCharge(devicePowerMah));
- if (powerModel != BatteryConsumer.POWER_MODEL_UNDEFINED
- && powerModel != BatteryConsumer.POWER_MODEL_POWER_PROFILE) {
- sb.append(" [");
- sb.append(BatteryConsumer.powerModelToString(powerModel));
- sb.append("]");
- }
sb.append(" apps: ").append(BatteryStats.formatCharge(appsPowerMah));
if (durationMs != 0) {
sb.append(" duration: ");
@@ -828,7 +812,7 @@ public final class BatteryUsageStats implements Parcelable, Closeable {
final boolean includesPowerStateData = parser.getAttributeBoolean(null,
XML_ATTR_PREFIX_INCLUDES_POWER_STATE_DATA, false);
- builder = new Builder(customComponentNames.toArray(new String[0]), true,
+ builder = new Builder(customComponentNames.toArray(new String[0]),
includesProcStateData, includesScreenStateData, includesPowerStateData, 0);
builder.setStatsStartTimestamp(
@@ -913,7 +897,6 @@ public final class BatteryUsageStats implements Parcelable, Closeable {
private final CursorWindow mBatteryConsumersCursorWindow;
@NonNull
private final String[] mCustomPowerComponentNames;
- private final boolean mIncludePowerModels;
private final boolean mIncludesProcessStateData;
private final boolean mIncludesScreenStateData;
private final boolean mIncludesPowerStateData;
@@ -938,22 +921,21 @@ public final class BatteryUsageStats implements Parcelable, Closeable {
private BatteryStatsHistory mBatteryStatsHistory;
public Builder(@NonNull String[] customPowerComponentNames) {
- this(customPowerComponentNames, false, false, false, false, 0);
+ this(customPowerComponentNames, false, false, false, 0);
}
- public Builder(@NonNull String[] customPowerComponentNames, boolean includePowerModels,
+ public Builder(@NonNull String[] customPowerComponentNames,
boolean includeProcessStateData, boolean includeScreenStateData,
boolean includesPowerStateData, double minConsumedPowerThreshold) {
mBatteryConsumersCursorWindow =
new CursorWindow(null, BATTERY_CONSUMER_CURSOR_WINDOW_SIZE);
onCursorWindowAllocated(mBatteryConsumersCursorWindow);
mBatteryConsumerDataLayout = BatteryConsumer.createBatteryConsumerDataLayout(
- customPowerComponentNames, includePowerModels, includeProcessStateData,
+ customPowerComponentNames, includeProcessStateData,
includeScreenStateData, includesPowerStateData);
mBatteryConsumersCursorWindow.setNumColumns(mBatteryConsumerDataLayout.columnCount);
mCustomPowerComponentNames = customPowerComponentNames;
- mIncludePowerModels = includePowerModels;
mIncludesProcessStateData = includeProcessStateData;
mIncludesScreenStateData = includeScreenStateData;
mIncludesPowerStateData = includesPowerStateData;
diff --git a/core/java/android/os/BatteryUsageStatsQuery.java b/core/java/android/os/BatteryUsageStatsQuery.java
index 6325b003a999..6e67578fadc8 100644
--- a/core/java/android/os/BatteryUsageStatsQuery.java
+++ b/core/java/android/os/BatteryUsageStatsQuery.java
@@ -24,6 +24,7 @@ import com.android.internal.os.MonotonicClock;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
/**
* Query parameters for the {@link BatteryStatsManager#getBatteryUsageStats()} call.
@@ -65,12 +66,6 @@ public final class BatteryUsageStatsQuery implements Parcelable {
*/
public static final int FLAG_BATTERY_USAGE_STATS_INCLUDE_HISTORY = 0x0002;
- /**
- * Indicates that identifiers of power models used for computations of power
- * consumption should be included in the BatteryUsageStats.
- */
- public static final int FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_MODELS = 0x0004;
-
public static final int FLAG_BATTERY_USAGE_STATS_INCLUDE_PROCESS_STATE_DATA = 0x0008;
public static final int FLAG_BATTERY_USAGE_STATS_INCLUDE_VIRTUAL_UIDS = 0x0010;
@@ -202,7 +197,24 @@ public final class BatteryUsageStatsQuery implements Parcelable {
return mAggregatedToTimestamp;
}
+ @Override
+ public String toString() {
+ return "BatteryUsageStatsQuery{"
+ + "mFlags=" + Integer.toHexString(mFlags)
+ + ", mUserIds=" + Arrays.toString(mUserIds)
+ + ", mMaxStatsAgeMs=" + mMaxStatsAgeMs
+ + ", mAggregatedFromTimestamp=" + mAggregatedFromTimestamp
+ + ", mAggregatedToTimestamp=" + mAggregatedToTimestamp
+ + ", mMonotonicStartTime=" + mMonotonicStartTime
+ + ", mMonotonicEndTime=" + mMonotonicEndTime
+ + ", mMinConsumedPowerThreshold=" + mMinConsumedPowerThreshold
+ + ", mPowerComponents=" + Arrays.toString(mPowerComponents)
+ + '}';
+ }
+
private BatteryUsageStatsQuery(Parcel in) {
+ mMonotonicStartTime = in.readLong();
+ mMonotonicEndTime = in.readLong();
mFlags = in.readInt();
mUserIds = new int[in.readInt()];
in.readIntArray(mUserIds);
@@ -215,6 +227,8 @@ public final class BatteryUsageStatsQuery implements Parcelable {
@Override
public void writeToParcel(Parcel dest, int flags) {
+ dest.writeLong(mMonotonicStartTime);
+ dest.writeLong(mMonotonicEndTime);
dest.writeInt(mFlags);
dest.writeInt(mUserIds.length);
dest.writeIntArray(mUserIds);
@@ -311,7 +325,10 @@ public final class BatteryUsageStatsQuery implements Parcelable {
* power monitoring data is available.
*
* Should only be used for testing and debugging.
+ *
+ * @deprecated PowerModel is no longer supported
*/
+ @Deprecated
public Builder powerProfileModeledOnly() {
mFlags |= BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_POWER_PROFILE_MODEL;
return this;
@@ -322,9 +339,10 @@ public final class BatteryUsageStatsQuery implements Parcelable {
* of power consumption.
*
* Should only be used for testing and debugging.
+ * @deprecated PowerModel is no longer supported
*/
+ @Deprecated
public Builder includePowerModels() {
- mFlags |= BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_MODELS;
return this;
}
diff --git a/core/java/android/os/CombinedMessageQueue/MessageQueue.java b/core/java/android/os/CombinedMessageQueue/MessageQueue.java
index 3e4d74546adc..476968151e18 100644
--- a/core/java/android/os/CombinedMessageQueue/MessageQueue.java
+++ b/core/java/android/os/CombinedMessageQueue/MessageQueue.java
@@ -126,7 +126,9 @@ public final class MessageQueue {
// Use this awful heuristic to detect them.
if (useConcurrent) {
final String processName = Process.myProcessName();
- if (processName.contains("test") || processName.contains("Test")) {
+ if (processName == null
+ || processName.contains("test")
+ || processName.contains("Test")) {
useConcurrent = false;
}
}
diff --git a/core/java/android/os/CpuHeadroomParams.java b/core/java/android/os/CpuHeadroomParams.java
index f0d4f7d8737f..8e78b7e355f9 100644
--- a/core/java/android/os/CpuHeadroomParams.java
+++ b/core/java/android/os/CpuHeadroomParams.java
@@ -18,10 +18,13 @@ package android.os;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
import android.os.health.SystemHealthManager;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
/**
* Headroom request params used by {@link SystemHealthManager#getCpuHeadroom(CpuHeadroomParams)}.
@@ -54,6 +57,16 @@ public final class CpuHeadroomParams {
public static final int CPU_HEADROOM_CALCULATION_TYPE_AVERAGE = 1;
/**
+ * Minimum CPU headroom calculation window size.
+ */
+ public static final int CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN = 50;
+
+ /**
+ * Maximum CPU headroom calculation window size.
+ */
+ public static final int CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX = 10000;
+
+ /**
* Sets the headroom calculation type.
* <p>
*
@@ -83,6 +96,63 @@ public final class CpuHeadroomParams {
}
/**
+ * Sets the headroom calculation window size in milliseconds.
+ * <p>
+ *
+ * @param windowMillis the window size in milliseconds, ranged from
+ * [{@link #CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN},
+ * {@link #CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX}]. The smaller
+ * the value, the larger fluctuation in value should be expected. The
+ * default value can be retrieved from the
+ * {@link #getCalculationWindowMillis}. The device will try to use the
+ * closest feasible window size to this param.
+ * @throws IllegalArgumentException if the window size is not in allowed range.
+ */
+ public void setCalculationWindowMillis(
+ @IntRange(from = CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN, to =
+ CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX) int windowMillis) {
+ if (windowMillis < CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN
+ || windowMillis > CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX) {
+ throw new IllegalArgumentException("Invalid calculation window: " + windowMillis);
+ }
+ mInternal.calculationWindowMillis = windowMillis;
+ }
+
+ /**
+ * Gets the headroom calculation window size in milliseconds.
+ * <p>
+ * This will return the default value chosen by the device if not set.
+ */
+ public @IntRange(from = CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN, to =
+ CPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX) long getCalculationWindowMillis() {
+ return mInternal.calculationWindowMillis;
+ }
+
+ /**
+ * Sets the thread TIDs to track.
+ * <p>
+ * The TIDs should belong to the same of the process that will the headroom call. And they
+ * should not have different core affinity.
+ * <p>
+ * If not set, the headroom will be based on the PID of the process making the call.
+ *
+ * @param tids non-empty list of TIDs, maximum 5.
+ * @throws IllegalArgumentException if the list size is not in allowed range or TID is not
+ * positive.
+ */
+ public void setTids(@NonNull int... tids) {
+ if (tids.length == 0 || tids.length > 5) {
+ throw new IllegalArgumentException("Invalid number of TIDs: " + tids.length);
+ }
+ for (int tid : tids) {
+ if (tid <= 0) {
+ throw new IllegalArgumentException("Invalid TID: " + tid);
+ }
+ }
+ mInternal.tids = Arrays.copyOf(tids, tids.length);
+ }
+
+ /**
* @hide
*/
public CpuHeadroomParamsInternal getInternal() {
diff --git a/core/java/android/os/CpuHeadroomParamsInternal.aidl b/core/java/android/os/CpuHeadroomParamsInternal.aidl
index 6cc4699a809e..d572f965579b 100644
--- a/core/java/android/os/CpuHeadroomParamsInternal.aidl
+++ b/core/java/android/os/CpuHeadroomParamsInternal.aidl
@@ -25,6 +25,8 @@ import android.hardware.power.CpuHeadroomParams;
@JavaDerive(equals = true, toString = true)
parcelable CpuHeadroomParamsInternal {
boolean usesDeviceHeadroom = false;
+ int[] tids;
+ int calculationWindowMillis = 1000;
CpuHeadroomParams.CalculationType calculationType = CpuHeadroomParams.CalculationType.MIN;
CpuHeadroomParams.SelectionType selectionType = CpuHeadroomParams.SelectionType.ALL;
}
diff --git a/core/java/android/os/GpuHeadroomParams.java b/core/java/android/os/GpuHeadroomParams.java
index efb2a28ad2b5..4dc98264e57b 100644
--- a/core/java/android/os/GpuHeadroomParams.java
+++ b/core/java/android/os/GpuHeadroomParams.java
@@ -18,6 +18,7 @@ package android.os;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
+import android.annotation.IntRange;
import android.os.health.SystemHealthManager;
import java.lang.annotation.Retention;
@@ -54,6 +55,16 @@ public final class GpuHeadroomParams {
public static final int GPU_HEADROOM_CALCULATION_TYPE_AVERAGE = 1;
/**
+ * Minimum GPU headroom calculation window size.
+ */
+ public static final int GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN = 50;
+
+ /**
+ * Maximum GPU headroom calculation window size.
+ */
+ public static final int GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX = 10000;
+
+ /**
* Sets the headroom calculation type.
* <p>
*
@@ -83,6 +94,39 @@ public final class GpuHeadroomParams {
}
/**
+ * Sets the headroom calculation window size in milliseconds.
+ * <p>
+ *
+ * @param windowMillis the window size in milliseconds, ranged from
+ * [{@link #GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN},
+ * {@link #GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX}]. The smaller
+ * the value, the larger fluctuation in value should be expected. The
+ * default value can be retrieved from the
+ * {@link #getCalculationWindowMillis}. If the device will try to use the
+ * closest feasible window size to this param.
+ * @throws IllegalArgumentException if the window is invalid.
+ */
+ public void setCalculationWindowMillis(
+ @IntRange(from = GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN, to =
+ GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX) int windowMillis) {
+ if (windowMillis < GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN
+ || windowMillis > GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX) {
+ throw new IllegalArgumentException("Invalid calculation window: " + windowMillis);
+ }
+ mInternal.calculationWindowMillis = windowMillis;
+ }
+
+ /**
+ * Gets the headroom calculation window size in milliseconds.
+ * <p>
+ * This will return the default value chosen by the device if not set.
+ */
+ public @IntRange(from = GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MIN, to =
+ GPU_HEADROOM_CALCULATION_WINDOW_MILLIS_MAX) int getCalculationWindowMillis() {
+ return mInternal.calculationWindowMillis;
+ }
+
+ /**
* @hide
*/
public GpuHeadroomParamsInternal getInternal() {
diff --git a/core/java/android/os/GpuHeadroomParamsInternal.aidl b/core/java/android/os/GpuHeadroomParamsInternal.aidl
index 20309e7673f2..40d5d8e409ef 100644
--- a/core/java/android/os/GpuHeadroomParamsInternal.aidl
+++ b/core/java/android/os/GpuHeadroomParamsInternal.aidl
@@ -24,5 +24,6 @@ import android.hardware.power.GpuHeadroomParams;
*/
@JavaDerive(equals = true, toString = true)
parcelable GpuHeadroomParamsInternal {
+ int calculationWindowMillis = 1000;
GpuHeadroomParams.CalculationType calculationType = GpuHeadroomParams.CalculationType.MIN;
}
diff --git a/core/java/android/os/IHintManager.aidl b/core/java/android/os/IHintManager.aidl
index a043da9b7a36..f1936b5e0ff9 100644
--- a/core/java/android/os/IHintManager.aidl
+++ b/core/java/android/os/IHintManager.aidl
@@ -21,7 +21,9 @@ import android.os.CpuHeadroomParamsInternal;
import android.os.GpuHeadroomParamsInternal;
import android.os.IHintSession;
import android.os.SessionCreationConfig;
+import android.hardware.power.CpuHeadroomResult;
import android.hardware.power.ChannelConfig;
+import android.hardware.power.GpuHeadroomResult;
import android.hardware.power.SessionConfig;
import android.hardware.power.SessionTag;
@@ -53,9 +55,9 @@ interface IHintManager {
*/
@nullable ChannelConfig getSessionChannel(in IBinder token);
oneway void closeSessionChannel();
- float[] getCpuHeadroom(in CpuHeadroomParamsInternal params);
+ @nullable CpuHeadroomResult getCpuHeadroom(in CpuHeadroomParamsInternal params);
long getCpuHeadroomMinIntervalMillis();
- float getGpuHeadroom(in GpuHeadroomParamsInternal params);
+ @nullable GpuHeadroomResult getGpuHeadroom(in GpuHeadroomParamsInternal params);
long getGpuHeadroomMinIntervalMillis();
/**
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index 94259d7cf819..f9789c19b0d5 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -91,6 +91,8 @@ per-file DdmSyncStageUpdater.java = sanglardf@google.com, rpaquay@google.com
# PerformanceHintManager
per-file CpuHeadroom*.aidl = file:/ADPF_OWNERS
per-file GpuHeadroom*.aidl = file:/ADPF_OWNERS
+per-file CpuHeadroom*.java = file:/ADPF_OWNERS
+per-file GpuHeadroom*.java = file:/ADPF_OWNERS
per-file PerformanceHintManager.java = file:/ADPF_OWNERS
per-file WorkDuration.java = file:/ADPF_OWNERS
per-file IHintManager.aidl = file:/ADPF_OWNERS
diff --git a/core/java/android/os/PowerComponents.java b/core/java/android/os/PowerComponents.java
index d116e0737c46..4db1f1b32407 100644
--- a/core/java/android/os/PowerComponents.java
+++ b/core/java/android/os/PowerComponents.java
@@ -15,7 +15,6 @@
*/
package android.os;
-import static android.os.BatteryConsumer.BatteryConsumerDataLayout.POWER_MODEL_NOT_INCLUDED;
import static android.os.BatteryConsumer.POWER_COMPONENT_ANY;
import static android.os.BatteryConsumer.POWER_COMPONENT_BASE;
import static android.os.BatteryConsumer.POWER_STATE_ANY;
@@ -156,15 +155,6 @@ class PowerComponents {
return mData.layout.getPowerComponentName(componentId);
}
- @BatteryConsumer.PowerModel
- int getPowerModel(BatteryConsumer.Key key) {
- if (key.mPowerModelColumnIndex == POWER_MODEL_NOT_INCLUDED) {
- throw new IllegalStateException(
- "Power model IDs were not requested in the BatteryUsageStatsQuery");
- }
- return mData.getInt(key.mPowerModelColumnIndex);
- }
-
/**
* Returns the amount of time used by the specified component, e.g. CPU, WiFi etc.
*
@@ -378,10 +368,6 @@ class PowerComponents {
if (durationMs != 0) {
serializer.attributeLong(null, BatteryUsageStats.XML_ATTR_DURATION, durationMs);
}
- if (mData.layout.powerModelsIncluded) {
- serializer.attributeInt(null, BatteryUsageStats.XML_ATTR_MODEL,
- getPowerModel(key));
- }
serializer.endTag(null, BatteryUsageStats.XML_TAG_COMPONENT);
}
serializer.endTag(null, BatteryUsageStats.XML_TAG_POWER_COMPONENTS);
@@ -411,7 +397,6 @@ class PowerComponents {
int powerState = POWER_STATE_UNSPECIFIED;
double powerMah = 0;
long durationMs = 0;
- int model = BatteryConsumer.POWER_MODEL_UNDEFINED;
for (int i = 0; i < parser.getAttributeCount(); i++) {
switch (parser.getAttributeName(i)) {
case BatteryUsageStats.XML_ATTR_ID:
@@ -432,14 +417,11 @@ class PowerComponents {
case BatteryUsageStats.XML_ATTR_DURATION:
durationMs = parser.getAttributeLong(i);
break;
- case BatteryUsageStats.XML_ATTR_MODEL:
- model = parser.getAttributeInt(i);
- break;
}
}
final BatteryConsumer.Key key = builder.mData.layout.getKey(componentId,
processState, screenState, powerState);
- builder.addConsumedPower(key, powerMah, model);
+ builder.addConsumedPower(key, powerMah);
builder.addUsageDurationMillis(key, durationMs);
break;
}
@@ -453,43 +435,28 @@ class PowerComponents {
* Builder for PowerComponents.
*/
static final class Builder {
- private static final byte POWER_MODEL_UNINITIALIZED = -1;
-
private final BatteryConsumer.BatteryConsumerData mData;
private final double mMinConsumedPowerThreshold;
Builder(BatteryConsumer.BatteryConsumerData data, double minConsumedPowerThreshold) {
mData = data;
mMinConsumedPowerThreshold = minConsumedPowerThreshold;
- for (BatteryConsumer.Key key : mData.layout.keys) {
- if (key.mPowerModelColumnIndex != POWER_MODEL_NOT_INCLUDED) {
- mData.putInt(key.mPowerModelColumnIndex, POWER_MODEL_UNINITIALIZED);
- }
- }
}
/**
- * @deprecated use {@link #addConsumedPower(BatteryConsumer.Key, double, int)}
+ * @deprecated use {@link #addConsumedPower(BatteryConsumer.Key, double)}
*/
@Deprecated
@NonNull
- public Builder setConsumedPower(BatteryConsumer.Key key, double componentPower,
- int powerModel) {
+ public Builder setConsumedPower(BatteryConsumer.Key key, double componentPower) {
mData.putDouble(key.mPowerColumnIndex, componentPower);
- if (key.mPowerModelColumnIndex != POWER_MODEL_NOT_INCLUDED) {
- mData.putInt(key.mPowerModelColumnIndex, powerModel);
- }
return this;
}
@NonNull
- public Builder addConsumedPower(BatteryConsumer.Key key, double componentPower,
- int powerModel) {
+ public Builder addConsumedPower(BatteryConsumer.Key key, double componentPower) {
mData.putDouble(key.mPowerColumnIndex,
mData.getDouble(key.mPowerColumnIndex) + componentPower);
- if (key.mPowerModelColumnIndex != POWER_MODEL_NOT_INCLUDED) {
- mData.putInt(key.mPowerModelColumnIndex, powerModel);
- }
return this;
}
@@ -547,28 +514,6 @@ class PowerComponents {
mData.getLong(key.mDurationColumnIndex)
+ otherData.getLong(otherKey.mDurationColumnIndex));
}
- if (key.mPowerModelColumnIndex == POWER_MODEL_NOT_INCLUDED) {
- continue;
- }
-
- boolean undefined = false;
- if (otherKey.mPowerModelColumnIndex == POWER_MODEL_NOT_INCLUDED) {
- undefined = true;
- } else {
- final int powerModel = mData.getInt(key.mPowerModelColumnIndex);
- int otherPowerModel = otherData.getInt(otherKey.mPowerModelColumnIndex);
- if (powerModel == POWER_MODEL_UNINITIALIZED) {
- mData.putInt(key.mPowerModelColumnIndex, otherPowerModel);
- } else if (powerModel != otherPowerModel
- && otherPowerModel != POWER_MODEL_UNINITIALIZED) {
- undefined = true;
- }
- }
-
- if (undefined) {
- mData.putInt(key.mPowerModelColumnIndex,
- BatteryConsumer.POWER_MODEL_UNDEFINED);
- }
}
}
@@ -594,13 +539,6 @@ class PowerComponents {
@NonNull
public PowerComponents build() {
for (BatteryConsumer.Key key : mData.layout.keys) {
- if (key.mPowerModelColumnIndex != POWER_MODEL_NOT_INCLUDED) {
- if (mData.getInt(key.mPowerModelColumnIndex) == POWER_MODEL_UNINITIALIZED) {
- mData.putInt(key.mPowerModelColumnIndex,
- BatteryConsumer.POWER_MODEL_UNDEFINED);
- }
- }
-
if (mMinConsumedPowerThreshold != 0) {
if (mData.getDouble(key.mPowerColumnIndex) < mMinConsumedPowerThreshold) {
mData.putDouble(key.mPowerColumnIndex, 0);
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index e5340a70acec..3001fbd4fafa 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -231,6 +231,15 @@ flag {
}
flag {
+ name: "message_queue_testability"
+ namespace: "system_performance"
+ is_exported: true
+ description: "Whether MessageQueue implements test APIs."
+ bug: "379472827"
+ is_fixed_read_only: true
+}
+
+flag {
name: "network_time_uses_shared_memory"
namespace: "system_performance"
description: "SystemClock.currentNetworkTimeMillis() reads network time offset from shared memory"
diff --git a/core/java/android/os/health/SystemHealthManager.java b/core/java/android/os/health/SystemHealthManager.java
index 4db9bc333e2b..cd79e416531a 100644
--- a/core/java/android/os/health/SystemHealthManager.java
+++ b/core/java/android/os/health/SystemHealthManager.java
@@ -23,6 +23,8 @@ import android.annotation.Nullable;
import android.annotation.SystemService;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
+import android.hardware.power.CpuHeadroomResult;
+import android.hardware.power.GpuHeadroomResult;
import android.os.BatteryStats;
import android.os.Build;
import android.os.Bundle;
@@ -110,15 +112,16 @@ public class SystemHealthManager {
}
/**
- * Provides an estimate of global available CPU headroom of the calling thread.
+ * Provides an estimate of global available CPU headroom.
* <p>
*
* @param params params to customize the CPU headroom calculation, null to use default params.
- * @return a single value a {@code Float.NaN} if it's temporarily unavailable.
+ * @return a single value headroom or a {@code Float.NaN} if it's temporarily unavailable.
* A valid value is ranged from [0, 100], where 0 indicates no more CPU resources can be
* granted.
- * @throws UnsupportedOperationException if the API is unsupported or the request params can't
- * be served.
+ * @throws UnsupportedOperationException if the API is unsupported.
+ * @throws SecurityException if the TIDs of the params don't belong to the same process.
+ * @throws IllegalStateException if the TIDs of the params don't have the same affinity setting.
*/
@FlaggedApi(android.os.Flags.FLAG_CPU_GPU_HEADROOMS)
public @FloatRange(from = 0f, to = 100f) float getCpuHeadroom(
@@ -127,8 +130,12 @@ public class SystemHealthManager {
throw new UnsupportedOperationException();
}
try {
- return mHintManager.getCpuHeadroom(
- params != null ? params.getInternal() : new CpuHeadroomParamsInternal())[0];
+ final CpuHeadroomResult ret = mHintManager.getCpuHeadroom(
+ params != null ? params.getInternal() : new CpuHeadroomParamsInternal());
+ if (ret == null || ret.getTag() != CpuHeadroomResult.globalHeadroom) {
+ return Float.NaN;
+ }
+ return ret.getGlobalHeadroom();
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
@@ -144,8 +151,7 @@ public class SystemHealthManager {
* @return a single value headroom or a {@code Float.NaN} if it's temporarily unavailable.
* A valid value is ranged from [0, 100], where 0 indicates no more GPU resources can be
* granted.
- * @throws UnsupportedOperationException if the API is unsupported or the request params can't
- * be served.
+ * @throws UnsupportedOperationException if the API is unsupported.
*/
@FlaggedApi(android.os.Flags.FLAG_CPU_GPU_HEADROOMS)
public @FloatRange(from = 0f, to = 100f) float getGpuHeadroom(
@@ -154,8 +160,12 @@ public class SystemHealthManager {
throw new UnsupportedOperationException();
}
try {
- return mHintManager.getGpuHeadroom(
+ final GpuHeadroomResult ret = mHintManager.getGpuHeadroom(
params != null ? params.getInternal() : new GpuHeadroomParamsInternal());
+ if (ret == null || ret.getTag() != GpuHeadroomResult.globalHeadroom) {
+ return Float.NaN;
+ }
+ return ret.getGlobalHeadroom();
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index 55ba4afc9c68..a7195834e6bb 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -58,7 +58,7 @@ flag {
is_fixed_read_only: true
namespace: "permissions"
description: "enable enhanced confirmation incall apis"
- bug: "310220212"
+ bug: "364535720"
}
flag {
@@ -67,7 +67,7 @@ flag {
is_fixed_read_only: true
namespace: "permissions"
description: "enable the blocking of certain app installs during an unknown call"
- bug: "310220212"
+ bug: "364535720"
}
flag {
@@ -436,3 +436,12 @@ flag {
description: "Use profile labels from UserManager for default app section titles to allow partner customization"
bug: "358369931"
}
+
+flag {
+ name: "wallet_role_cross_user_enabled"
+ is_exported: true
+ is_fixed_read_only: true
+ namespace: "wallet_integration"
+ description: "Enable the Wallet role within profiles"
+ bug: "356107987"
+}
diff --git a/core/java/android/security/responsible_apis_flags.aconfig b/core/java/android/security/responsible_apis_flags.aconfig
index 357aba3fb136..a5c837b88fa4 100644
--- a/core/java/android/security/responsible_apis_flags.aconfig
+++ b/core/java/android/security/responsible_apis_flags.aconfig
@@ -125,3 +125,16 @@ flag {
description: "Android Advanced Protection Mode Feature: Disable Install Unknown Sources"
bug: "369361373"
}
+
+flag {
+ name: "aapm_feature_memory_tagging_extension"
+ namespace: "responsible_apis"
+ description: "Android Advanced Protection Mode Feature: Memory Tagging Extension"
+ bug: "378931989"
+}
+flag {
+ name: "aapm_feature_disable_cellular_2g"
+ namespace: "responsible_apis"
+ description: "Android Advanced Protection Mode Feature: Disable Cellular 2G"
+ bug: "377748286"
+}
diff --git a/core/java/android/service/quickaccesswallet/flags.aconfig b/core/java/android/service/quickaccesswallet/flags.aconfig
index 75a93091eec3..7225f27c4555 100644
--- a/core/java/android/service/quickaccesswallet/flags.aconfig
+++ b/core/java/android/service/quickaccesswallet/flags.aconfig
@@ -6,4 +6,11 @@ flag {
namespace: "wallet_integration"
description: "Option to launch the Wallet app on double-tap of the power button"
bug: "378469025"
+}
+
+flag {
+ name: "launch_selected_card_from_qs_tile"
+ namespace: "wallet_integration"
+ description: "When the wallet QS tile is tapped, launch the selected card pending intent instead of the home screen pending intent."
+ bug: "378469025"
} \ No newline at end of file
diff --git a/core/java/android/view/ScrollCaptureSearchResults.java b/core/java/android/view/ScrollCaptureSearchResults.java
index 3469b9dc7103..b1ce949f9919 100644
--- a/core/java/android/view/ScrollCaptureSearchResults.java
+++ b/core/java/android/view/ScrollCaptureSearchResults.java
@@ -16,10 +16,16 @@
package android.view;
+import static android.view.flags.Flags.scrollCaptureTargetZOrderFix;
+
+import static java.util.Comparator.comparing;
import static java.util.Objects.requireNonNull;
+import static java.util.Objects.requireNonNullElse;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.UiThread;
+import android.graphics.Point;
import android.graphics.Rect;
import android.os.CancellationSignal;
import android.util.IndentingPrintWriter;
@@ -113,7 +119,9 @@ public final class ScrollCaptureSearchResults {
private void signalComplete() {
mComplete = true;
- mTargets.sort(PRIORITY_ORDER);
+ if (!scrollCaptureTargetZOrderFix()) {
+ mTargets.sort(PRIORITY_ORDER);
+ }
if (mOnCompleteListener != null) {
mOnCompleteListener.run();
mOnCompleteListener = null;
@@ -125,14 +133,73 @@ public final class ScrollCaptureSearchResults {
return new ArrayList<>(mTargets);
}
+ private Rect getScrollBoundsInWindow(@Nullable ScrollCaptureTarget target) {
+ if (target == null || target.getScrollBounds() == null) {
+ return new Rect();
+ }
+ Rect windowRect = new Rect(target.getScrollBounds());
+ Point windowPosition = target.getPositionInWindow();
+ windowRect.offset(windowPosition.x, windowPosition.y);
+ return windowRect;
+ }
+
/**
* Get the top ranked result out of all completed requests.
*
* @return the top ranked result
*/
+ @Nullable
public ScrollCaptureTarget getTopResult() {
- ScrollCaptureTarget target = mTargets.isEmpty() ? null : mTargets.get(0);
- return target != null && target.getScrollBounds() != null ? target : null;
+ if (!scrollCaptureTargetZOrderFix()) {
+ ScrollCaptureTarget target = mTargets.isEmpty() ? null : mTargets.get(0);
+ return target != null && target.getScrollBounds() != null ? target : null;
+ }
+ List<ScrollCaptureTarget> filtered = new ArrayList<>();
+
+ mTargets.removeIf(a -> nullOrEmpty(a.getScrollBounds()));
+
+ // Remove scroll targets obscured or covered by other scrolling views.
+ nextTarget:
+ for (int i = 0; i < mTargets.size(); i++) {
+ ScrollCaptureTarget current = mTargets.get(i);
+
+ View currentView = current.getContainingView();
+
+ // Nested scroll containers:
+ // Check if the next view is a child of the current. If so, skip the current.
+ if (i + 1 < mTargets.size()) {
+ ScrollCaptureTarget next = mTargets.get(i + 1);
+ View nextView = next.getContainingView();
+ // Honor explicit include hint on parent as escape hatch (unless both have it)
+ if (isDescendant(currentView, nextView)
+ && (!hasIncludeHint(currentView) || hasIncludeHint(nextView))) {
+ continue;
+ }
+ }
+
+ // Check if any views will be drawn partially or fully over this one.
+ for (int j = i + 1; j < mTargets.size(); j++) {
+ ScrollCaptureTarget above = mTargets.get(j);
+ if (Rect.intersects(getScrollBoundsInWindow(current),
+ getScrollBoundsInWindow(above))) {
+ continue nextTarget;
+ }
+ }
+
+ filtered.add(current);
+ }
+
+ // natural order, false->true
+ Comparator<ScrollCaptureTarget> byIncludeHintPresence = comparing(
+ ScrollCaptureSearchResults::hasIncludeHint);
+
+ // natural order, smallest->largest area
+ Comparator<ScrollCaptureTarget> byArea = comparing(
+ target -> area(requireNonNullElse(target.getScrollBounds(), new Rect())));
+
+ // The top result is the last one (with include hint if present, then by largest area)
+ filtered.sort(byIncludeHintPresence.thenComparing(byArea));
+ return filtered.isEmpty() ? null : filtered.getLast();
}
private class SearchRequest implements Consumer<Rect> {
@@ -226,6 +293,10 @@ public final class ScrollCaptureSearchResults {
return r == null || r.isEmpty();
}
+ private static boolean hasIncludeHint(ScrollCaptureTarget target) {
+ return hasIncludeHint(target.getContainingView());
+ }
+
private static boolean hasIncludeHint(View view) {
return (view.getScrollCaptureHint() & View.SCROLL_CAPTURE_HINT_INCLUDE) != 0;
}
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 4a9916c007c4..949b667f0b7a 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -20,6 +20,7 @@ import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_CONTINUE
import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP;
import static android.view.flags.Flags.FLAG_TOOLKIT_VIEWGROUP_SET_REQUESTED_FRAME_RATE_API;
import static android.view.flags.Flags.toolkitViewgroupSetRequestedFrameRateApi;
+import static android.view.flags.Flags.scrollCaptureTargetZOrderFix;
import android.animation.LayoutTransition;
import android.annotation.CallSuper;
@@ -7657,6 +7658,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
@NonNull Rect localVisibleRect, @NonNull Point windowOffset,
@NonNull Consumer<ScrollCaptureTarget> targets) {
+ // Only visible views can be captured.
+ if (getVisibility() != View.VISIBLE) {
+ return;
+ }
+
if (getClipToPadding() && !localVisibleRect.intersect(mPaddingLeft, mPaddingTop,
(mRight - mLeft) - mPaddingRight, (mBottom - mTop) - mPaddingBottom)) {
return;
@@ -7665,19 +7671,39 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
// Dispatch to self first.
super.dispatchScrollCaptureSearch(localVisibleRect, windowOffset, targets);
+ final int childrenCount = mChildrenCount;
+ if (childrenCount == 0) {
+ return;
+ }
+
// Skip children if descendants excluded.
if ((getScrollCaptureHint() & SCROLL_CAPTURE_HINT_EXCLUDE_DESCENDANTS) != 0) {
return;
}
-
final Rect tmpRect = getTempRect();
- final int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- View child = getChildAt(i);
+
+ ArrayList<View> preorderedList = null;
+ boolean customOrder = false;
+ if (scrollCaptureTargetZOrderFix()) {
+ preorderedList = buildOrderedChildList();
+ customOrder = preorderedList == null && isChildrenDrawingOrderEnabled();
+ }
+ final View[] children = mChildren;
+ for (int i = 0; i < childrenCount; i++) {
+ View child;
+ if (scrollCaptureTargetZOrderFix()) {
+ // Traverse children in the same order they will be drawn (honors Z if set)
+ final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
+ child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
+ } else {
+ child = children[i];
+ }
+
// Only visible views can be captured.
if (child.getVisibility() != View.VISIBLE) {
continue;
}
+
// Offset the given rectangle (in parent's local coordinates) into child's coordinate
// space and clip the result to the child View's bounds, padding and clipRect as needed.
// If the resulting rectangle is not empty, the request is forwarded to the child.
@@ -7706,6 +7732,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
child.dispatchScrollCaptureSearch(tmpRect, childWindowOffset, targets);
}
}
+ if (preorderedList != null) {
+ preorderedList.clear();
+ }
}
/**
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index b6e114b3a3ca..a0feccd87a81 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1236,6 +1236,8 @@ public final class ViewRootImpl implements ViewParent,
private @ActivityInfo.ColorMode int mCurrentColorMode = ActivityInfo.COLOR_MODE_DEFAULT;
private long mColorModeLastSetMillis = -1;
+ private final boolean mIsSubscribeGranularDisplayEventsEnabled;
+
public ViewRootImpl(Context context, Display display) {
this(context, display, WindowManagerGlobal.getWindowSession(), new WindowLayout());
}
@@ -1333,6 +1335,8 @@ public final class ViewRootImpl implements ViewParent,
// Disable DRAW_WAKE_LOCK starting U.
mDisableDrawWakeLock =
CompatChanges.isChangeEnabled(DISABLE_DRAW_WAKE_LOCK) && disableDrawWakeLock();
+ mIsSubscribeGranularDisplayEventsEnabled =
+ com.android.server.display.feature.flags.Flags.subscribeGranularDisplayEvents();
}
public static void addFirstDrawHandler(Runnable callback) {
@@ -1810,14 +1814,22 @@ public final class ViewRootImpl implements ViewParent,
mAccessibilityInteractionConnectionManager, mHandler);
mAccessibilityManager.addHighContrastTextStateChangeListener(
mExecutor, mHighContrastTextManager);
+
+
+ long eventsToBeRegistered =
+ (mIsSubscribeGranularDisplayEventsEnabled)
+ ? DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED
+ | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_STATE
+ | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED
+ : DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED
+ | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED
+ | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED;
DisplayManagerGlobal
.getInstance()
.registerDisplayListener(
mDisplayListener,
mHandler,
- DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED
- | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED
- | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED,
+ eventsToBeRegistered,
mBasePackageName);
if (forceInvertColor()) {
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index 0dfaf4149ce5..88ccf88d40f6 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -959,6 +959,8 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
CONTENT_CHANGE_TYPE_CONTENT_INVALID,
CONTENT_CHANGE_TYPE_ERROR,
CONTENT_CHANGE_TYPE_ENABLED,
+ CONTENT_CHANGE_TYPE_CHECKED,
+ CONTENT_CHANGE_TYPE_EXPANDED,
CONTENT_CHANGE_TYPE_SUPPLEMENTAL_DESCRIPTION,
})
public @interface ContentChangeTypes {}
@@ -1241,6 +1243,16 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
case CONTENT_CHANGE_TYPE_ERROR: return "CONTENT_CHANGE_TYPE_ERROR";
case CONTENT_CHANGE_TYPE_ENABLED: return "CONTENT_CHANGE_TYPE_ENABLED";
default: {
+ if (Flags.triStateChecked()) {
+ if (type == CONTENT_CHANGE_TYPE_CHECKED) {
+ return "CONTENT_CHANGE_TYPE_CHECKED";
+ }
+ }
+ if (Flags.a11yExpansionStateApi()) {
+ if (type == CONTENT_CHANGE_TYPE_EXPANDED) {
+ return "CONTENT_CHANGE_TYPE_EXPANDED";
+ }
+ }
if (Flags.supplementalDescription()) {
if (type == CONTENT_CHANGE_TYPE_SUPPLEMENTAL_DESCRIPTION) {
return "CONTENT_CHANGE_TYPE_SUPPLEMENTAL_DESCRIPTION";
diff --git a/core/java/android/view/flags/scroll_capture.aconfig b/core/java/android/view/flags/scroll_capture.aconfig
new file mode 100644
index 000000000000..fdf9c1ed8ad2
--- /dev/null
+++ b/core/java/android/view/flags/scroll_capture.aconfig
@@ -0,0 +1,13 @@
+package: "android.view.flags"
+container: "system"
+
+flag {
+ name: "scroll_capture_target_z_order_fix"
+ namespace: "system_ui"
+ description: "Always prefer targets with higher z-order"
+ bug: "365969802"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java
index 6e43d0f1c6cd..2a8a92882310 100644
--- a/core/java/android/widget/ProgressBar.java
+++ b/core/java/android/widget/ProgressBar.java
@@ -16,6 +16,8 @@
package android.widget;
+import static android.view.accessibility.Flags.indeterminateRangeInfo;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
@@ -2364,15 +2366,22 @@ public class ProgressBar extends View {
public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfoInternal(info);
- if (!isIndeterminate()) {
- AccessibilityNodeInfo.RangeInfo rangeInfo = AccessibilityNodeInfo.RangeInfo.obtain(
+ AccessibilityNodeInfo.RangeInfo rangeInfo = null;
+ if (isIndeterminate()) {
+ if (indeterminateRangeInfo()) {
+ rangeInfo = AccessibilityNodeInfo.RangeInfo.INDETERMINATE;
+ }
+ } else {
+ rangeInfo = new AccessibilityNodeInfo.RangeInfo(
AccessibilityNodeInfo.RangeInfo.RANGE_TYPE_INT, getMin(), getMax(),
getProgress());
- info.setRangeInfo(rangeInfo);
}
- // Only set the default state description when custom state descripton is null.
+ info.setRangeInfo(rangeInfo);
+
+ // Only set the default state description when custom state description is null.
if (getStateDescription() == null) {
+ // TODO(b/380340432): Remove after accessibility services stop relying on this.
if (isIndeterminate()) {
info.setStateDescription(getResources().getString(R.string.in_progress));
} else {
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 80d39d1f9a1c..7e3b90444429 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -1298,7 +1298,13 @@ public class RemoteViews implements Parcelable, Filter {
@Override
public void visitUris(@NonNull Consumer<Uri> visitor) {
- if (mIntentId != -1 || mItems == null) {
+ if (mItems == null) {
+ // Null item indicates adapter conversion took place, so the URIs in cached items
+ // need to be validated.
+ RemoteCollectionItems cachedItems = mCollectionCache.getItemsForId(mIntentId);
+ if (cachedItems != null) {
+ cachedItems.visitUris(visitor);
+ }
return;
}
@@ -9333,7 +9339,11 @@ public class RemoteViews implements Parcelable, Filter {
Set<Integer> bitmapIdSet = getBitmapIdsUsedByActions(new HashSet<>());
int result = 0;
for (int bitmapId: bitmapIdSet) {
- result += mBitmapCache.getBitmapForId(bitmapId).getAllocationByteCount();
+ Bitmap currentBitmap = mBitmapCache.getBitmapForId(bitmapId);
+ if (currentBitmap == null) {
+ continue;
+ }
+ result += currentBitmap.getAllocationByteCount();
}
return result;
diff --git a/core/java/android/window/WindowContext.java b/core/java/android/window/WindowContext.java
index fdaab66f2de3..2b370b9797e5 100644
--- a/core/java/android/window/WindowContext.java
+++ b/core/java/android/window/WindowContext.java
@@ -94,6 +94,23 @@ public class WindowContext extends ContextWrapper implements WindowProvider {
mController.attachToDisplayArea(mType, getDisplayId(), mOptions);
}
+ /**
+ * Updates this context to a new displayId.
+ * <p>
+ * Note that this doesn't re-parent previously attached windows (they should be removed and
+ * re-added manually after this is called). Resources associated with this context will have
+ * the correct value and configuration for the new display after this is called.
+ */
+ @Override
+ public void updateDisplay(int displayId) {
+ if (displayId == getDisplayId()) {
+ return;
+ }
+ super.updateDisplay(displayId);
+ mController.detachIfNeeded();
+ mController.attachToDisplayArea(mType, displayId, mOptions);
+ }
+
@Override
public Object getSystemService(String name) {
if (WINDOW_SERVICE.equals(name)) {
diff --git a/core/java/com/android/internal/content/NativeLibraryHelper.java b/core/java/com/android/internal/content/NativeLibraryHelper.java
index 0c56c677e06f..6ad7fef95357 100644
--- a/core/java/com/android/internal/content/NativeLibraryHelper.java
+++ b/core/java/com/android/internal/content/NativeLibraryHelper.java
@@ -26,6 +26,7 @@ import static android.system.OsConstants.S_IXGRP;
import static android.system.OsConstants.S_IXOTH;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.parsing.ApkLiteParseUtils;
import android.content.pm.parsing.PackageLite;
@@ -177,6 +178,13 @@ public class NativeLibraryHelper {
private native static int nativeCopyNativeBinaries(long handle, String sharedLibraryPath,
String abiToCopy, boolean extractNativeLibs, boolean debuggable);
+ private static native int nativeCheckAlignment(
+ long handle,
+ String sharedLibraryPath,
+ String abi,
+ boolean extractNativeLibs,
+ boolean debuggable);
+
private static long sumNativeBinaries(Handle handle, String abi) {
long sum = 0;
for (long apkHandle : handle.apkHandles) {
@@ -432,6 +440,51 @@ public class NativeLibraryHelper {
}
}
+ /**
+ * Checks alignment of APK and native libraries for 16KB device
+ *
+ * @param handle APK file to scan for native libraries
+ * @param libraryRoot directory for libraries
+ * @param abiOverride abiOverride for package
+ * @return {@link Modes from ApplicationInfo.PageSizeAppCompat} if successful or error code
+ * which suggests undefined mode
+ */
+ @ApplicationInfo.PageSizeAppCompatFlags
+ public static int checkAlignmentForCompatMode(
+ Handle handle,
+ String libraryRoot,
+ boolean nativeLibraryRootRequiresIsa,
+ String abiOverride) {
+ // Keep the code below in sync with copyNativeBinariesForSupportedAbi
+ int abi = findSupportedAbi(handle, Build.SUPPORTED_64_BIT_ABIS);
+ if (abi < 0) {
+ return ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_ERROR;
+ }
+
+ final String supportedAbi = Build.SUPPORTED_64_BIT_ABIS[abi];
+ final String instructionSet = VMRuntime.getInstructionSet(supportedAbi);
+ String subDir = libraryRoot;
+ if (nativeLibraryRootRequiresIsa) {
+ subDir += "/" + instructionSet;
+ }
+
+ int mode = ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED;
+ for (long apkHandle : handle.apkHandles) {
+ int res =
+ nativeCheckAlignment(
+ apkHandle,
+ subDir,
+ Build.SUPPORTED_64_BIT_ABIS[abi],
+ handle.extractNativeLibs,
+ handle.debuggable);
+ if (res == ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_ERROR) {
+ return res;
+ }
+ mode |= res;
+ }
+ return mode;
+ }
+
public static long sumNativeBinariesWithOverride(Handle handle, String abiOverride)
throws IOException {
long sum = 0;
diff --git a/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java b/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java
index 48d0d6c777de..5ec5762c0533 100644
--- a/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java
+++ b/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java
@@ -392,6 +392,10 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal,
private int memtagMode;
@ApplicationInfo.NativeHeapZeroInitialized
private int nativeHeapZeroInitialized;
+
+ @ApplicationInfo.PageSizeAppCompatFlags private int mPageSizeAppCompatFlags =
+ ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED;
+
@Nullable
@DataClass.ParcelWith(Parcelling.BuiltIn.ForBoolean.class)
private Boolean requestRawExternalStorageAccess;
@@ -1118,6 +1122,12 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal,
return nativeHeapZeroInitialized;
}
+ @ApplicationInfo.PageSizeAppCompatFlags
+ @Override
+ public int getPageSizeAppCompatFlags() {
+ return mPageSizeAppCompatFlags;
+ }
+
@Override
public int getNetworkSecurityConfigResourceId() {
return networkSecurityConfigRes;
@@ -2221,6 +2231,12 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal,
}
@Override
+ public PackageImpl setPageSizeAppCompatFlags(@ApplicationInfo.PageSizeAppCompatFlags int flag) {
+ mPageSizeAppCompatFlags = flag;
+ return this;
+ }
+
+ @Override
public PackageImpl setNetworkSecurityConfigResourceId(int value) {
networkSecurityConfigRes = value;
return this;
@@ -2703,6 +2719,7 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal,
appInfo.setKnownActivityEmbeddingCerts(mKnownActivityEmbeddingCerts);
}
appInfo.allowCrossUidActivitySwitchFromBelow = mAllowCrossUidActivitySwitchFromBelow;
+ appInfo.setPageSizeAppCompatFlags(mPageSizeAppCompatFlags);
return appInfo;
}
@@ -3305,6 +3322,7 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal,
dest.writeInt(this.mIntentMatchingFlags);
dest.writeIntArray(this.mAlternateLauncherIconResIds);
dest.writeIntArray(this.mAlternateLauncherLabelResIds);
+ dest.writeInt(this.mPageSizeAppCompatFlags);
}
private void writeFeatureFlagState(@NonNull Parcel dest) {
@@ -3499,6 +3517,7 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal,
this.mIntentMatchingFlags = in.readInt();
this.mAlternateLauncherIconResIds = in.createIntArray();
this.mAlternateLauncherLabelResIds = in.createIntArray();
+ this.mPageSizeAppCompatFlags = in.readInt();
assignDerivedFields();
assignDerivedFields2();
diff --git a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java
index 67b985a61455..5062d58d4dca 100644
--- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java
+++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java
@@ -31,7 +31,6 @@ import android.util.ArraySet;
import android.util.SparseArray;
import android.util.SparseIntArray;
-import com.android.internal.R;
import com.android.internal.pm.parsing.pkg.ParsedPackage;
import com.android.internal.pm.pkg.component.ParsedActivity;
import com.android.internal.pm.pkg.component.ParsedApexSystemService;
@@ -280,6 +279,9 @@ public interface ParsingPackage {
ParsingPackage setNativeHeapZeroInitialized(
@ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized);
+ /** Manifest option pageSizeCompat will populate this field */
+ ParsingPackage setPageSizeAppCompatFlags(@ApplicationInfo.PageSizeAppCompatFlags int value);
+
ParsingPackage setRequestRawExternalStorageAccess(
@Nullable Boolean requestRawExternalStorageAccess);
diff --git a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
index 5fc1276dd9f9..0f93e6e8109b 100644
--- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
@@ -541,6 +541,7 @@ public class ParsingPackageUtils {
pkg.setGwpAsanMode(-1);
pkg.setMemtagMode(-1);
+ pkg.setPageSizeAppCompatFlags(ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED);
afterParseBaseApplication(pkg);
@@ -2182,6 +2183,13 @@ public class ParsingPackageUtils {
pkg.setGwpAsanMode(sa.getInt(R.styleable.AndroidManifestApplication_gwpAsanMode, -1));
pkg.setMemtagMode(sa.getInt(R.styleable.AndroidManifestApplication_memtagMode, -1));
+
+ if (Flags.appCompatOption16kb()) {
+ pkg.setPageSizeAppCompatFlags(
+ sa.getInt(R.styleable.AndroidManifestApplication_pageSizeCompat,
+ ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED));
+ }
+
if (sa.hasValue(R.styleable.AndroidManifestApplication_nativeHeapZeroInitialized)) {
final boolean v = sa.getBoolean(
R.styleable.AndroidManifestApplication_nativeHeapZeroInitialized, false);
diff --git a/core/java/com/android/server/pm/pkg/AndroidPackage.java b/core/java/com/android/server/pm/pkg/AndroidPackage.java
index d05f5e3950b4..70dd10f2c371 100644
--- a/core/java/com/android/server/pm/pkg/AndroidPackage.java
+++ b/core/java/com/android/server/pm/pkg/AndroidPackage.java
@@ -875,6 +875,14 @@ public interface AndroidPackage {
int getMemtagMode();
/**
+ * @see ApplicationInfo#getPageSizeAppCompatFlags()
+ * @see R.styleable#AndroidManifestApplication_pageSizeCompat
+ * @hide
+ */
+ @ApplicationInfo.PageSizeAppCompatFlags
+ int getPageSizeAppCompatFlags();
+
+ /**
* TODO(b/135203078): Make all the Bundles immutable (and non-null by shared empty reference?)
* @see R.styleable#AndroidManifestMetaData
* @hide
diff --git a/core/jni/android_content_res_ApkAssets.cpp b/core/jni/android_content_res_ApkAssets.cpp
index ded1a9949ef8..1e7bfe32ba79 100644
--- a/core/jni/android_content_res_ApkAssets.cpp
+++ b/core/jni/android_content_res_ApkAssets.cpp
@@ -36,6 +36,8 @@ using ::android::base::unique_fd;
namespace android {
+static constexpr bool kLogWeakReachableDeletedAssets = false;
+
static struct overlayableinfo_offsets_t {
jclass classObject;
jmethodID constructor;
@@ -97,7 +99,7 @@ static void DeleteGuardedApkAssets(Guarded<AssetManager2::ApkAssetsPtr>& apk_ass
if (useCount > 1) {
ALOGW("ApkAssets: Deleting an object '%s' with %d > 1 strong and %d weak references",
(*assets)->GetDebugName().c_str(), int(useCount), int(weakCount));
- } else if (weakCount > 0) {
+ } else if constexpr (kLogWeakReachableDeletedAssets) if (weakCount > 0) {
ALOGW("ApkAssets: Deleting an ApkAssets object '%s' with %d weak references",
(*assets)->GetDebugName().c_str(), int(weakCount));
}
diff --git a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
index 7ad18b83f0d6..b2eeff36c007 100644
--- a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
+++ b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
@@ -21,6 +21,7 @@
#include <androidfw/ApkParsing.h>
#include <androidfw/ZipFileRO.h>
#include <androidfw/ZipUtils.h>
+#include <elf.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
@@ -38,6 +39,7 @@
#include <memory>
#include <string>
+#include <vector>
#include "com_android_internal_content_FileSystemUtils.h"
#include "core_jni_helpers.h"
@@ -60,6 +62,12 @@ enum install_status_t {
NO_NATIVE_LIBRARIES = -114
};
+// These code should match with PageSizeAppCompatFlags inside ApplicationInfo.java
+constexpr int PAGE_SIZE_APP_COMPAT_FLAG_ERROR = -1;
+constexpr int PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED = 0;
+constexpr int PAGE_SIZE_APP_COMPAT_FLAG_UNCOMPRESSED_LIBS_NOT_ALIGNED = 1 << 1;
+constexpr int PAGE_SIZE_APP_COMPAT_FLAG_ELF_NOT_ALIGNED = 1 << 2;
+
typedef install_status_t (*iterFunc)(JNIEnv*, void*, ZipFileRO*, ZipEntryRO, const char*);
static bool
@@ -524,11 +532,7 @@ static inline bool app_compat_16kb_enabled() {
static const size_t kPageSize = getpagesize();
// App compat is only applicable on 16kb-page-size devices.
- if (kPageSize != 0x4000) {
- return false;
- }
-
- return android::base::GetBoolProperty("bionic.linker.16kb.app_compat.enabled", false);
+ return kPageSize == 0x4000;
}
static jint
@@ -626,6 +630,166 @@ com_android_internal_content_NativeLibraryHelper_openApkFd(JNIEnv *env, jclass,
return reinterpret_cast<jlong>(zipFile);
}
+static jint checkLoadSegmentAlignment(const char* fileName, off64_t offset) {
+ std::vector<Elf64_Phdr> programHeaders;
+ if (!getLoadSegmentPhdrs(fileName, offset, programHeaders)) {
+ ALOGE("Failed to read program headers from ELF file.");
+ return PAGE_SIZE_APP_COMPAT_FLAG_ERROR;
+ }
+
+ int mode = PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED;
+ for (auto programHeader : programHeaders) {
+ if (programHeader.p_type != PT_LOAD) {
+ continue;
+ }
+
+ // Set ELF alignment bit if 4 KB aligned LOAD segment is found
+ if (programHeader.p_align == 0x1000) {
+ ALOGI("Setting page size compat mode PAGE_SIZE_APP_COMPAT_FLAG_ELF_NOT_ALIGNED");
+ mode |= PAGE_SIZE_APP_COMPAT_FLAG_ELF_NOT_ALIGNED;
+ break;
+ }
+ }
+
+ return mode;
+}
+
+static jint checkExtractedLibAlignment(ZipFileRO* zipFile, ZipEntryRO zipEntry,
+ const char* fileName, const std::string nativeLibPath) {
+ // Build local file path
+ const size_t fileNameLen = strlen(fileName);
+ char localFileName[nativeLibPath.size() + fileNameLen + 2];
+
+ if (strlcpy(localFileName, nativeLibPath.c_str(), sizeof(localFileName)) !=
+ nativeLibPath.size()) {
+ ALOGE("Couldn't allocate local file name for library");
+ return PAGE_SIZE_APP_COMPAT_FLAG_ERROR;
+ }
+
+ *(localFileName + nativeLibPath.size()) = '/';
+
+ if (strlcpy(localFileName + nativeLibPath.size() + 1, fileName,
+ sizeof(localFileName) - nativeLibPath.size() - 1) != fileNameLen) {
+ ALOGE("Couldn't allocate local file name for library");
+ return PAGE_SIZE_APP_COMPAT_FLAG_ERROR;
+ }
+
+ struct statfs64 fsInfo;
+ int result = statfs64(localFileName, &fsInfo);
+ if (result < 0) {
+ ALOGE("Failed to stat file :%s", localFileName);
+ return PAGE_SIZE_APP_COMPAT_FLAG_ERROR;
+ }
+
+ return checkLoadSegmentAlignment(localFileName, 0);
+}
+
+static jint checkAlignment(JNIEnv* env, jstring javaNativeLibPath, jboolean extractNativeLibs,
+ ZipFileRO* zipFile, ZipEntryRO zipEntry, const char* fileName) {
+ int mode = PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED;
+ // Need two separate install status for APK and ELF alignment
+ static const size_t kPageSize = getpagesize();
+ jint ret = INSTALL_SUCCEEDED;
+
+ ScopedUtfChars nativeLibPath(env, javaNativeLibPath);
+ if (extractNativeLibs) {
+ ALOGI("extractNativeLibs specified, checking for extracted lib %s", fileName);
+ return checkExtractedLibAlignment(zipFile, zipEntry, fileName, nativeLibPath.c_str());
+ }
+
+ uint16_t method;
+ off64_t offset;
+ if (!zipFile->getEntryInfo(zipEntry, &method, nullptr, nullptr, &offset, nullptr, nullptr,
+ nullptr)) {
+ ALOGE("Couldn't read zip entry info from zipFile %s", zipFile->getZipFileName());
+ return PAGE_SIZE_APP_COMPAT_FLAG_ERROR;
+ }
+
+ // check if library is uncompressed and page-aligned
+ if (method != ZipFileRO::kCompressStored) {
+ ALOGE("Library '%s' is compressed - will not be able to open it directly from apk.\n",
+ fileName);
+ return PAGE_SIZE_APP_COMPAT_FLAG_ERROR;
+ }
+
+ if (offset % kPageSize != 0) {
+ ALOGW("Library '%s' is not PAGE(%zu)-aligned - will not be able to open it directly "
+ "from apk.\n",
+ fileName, kPageSize);
+ mode |= PAGE_SIZE_APP_COMPAT_FLAG_UNCOMPRESSED_LIBS_NOT_ALIGNED;
+ ALOGI("Setting page size compat mode "
+ "PAGE_SIZE_APP_COMPAT_FLAG_UNCOMPRESSED_LIBS_NOT_ALIGNED for %s",
+ zipFile->getZipFileName());
+ }
+
+ int loadMode = checkLoadSegmentAlignment(zipFile->getZipFileName(), offset);
+ if (loadMode == PAGE_SIZE_APP_COMPAT_FLAG_ERROR) {
+ return PAGE_SIZE_APP_COMPAT_FLAG_ERROR;
+ }
+ mode |= loadMode;
+ return mode;
+}
+
+// TODO(b/371049373): This function is copy of iterateOverNativeFiles with different way of handling
+// and combining return values for all ELF and APKs. Find a way to consolidate two functions.
+static jint com_android_internal_content_NativeLibraryHelper_checkApkAlignment(
+ JNIEnv* env, jclass clazz, jlong apkHandle, jstring javaNativeLibPath, jstring javaCpuAbi,
+ jboolean extractNativeLibs, jboolean debuggable) {
+ int mode = PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED;
+ ZipFileRO* zipFile = reinterpret_cast<ZipFileRO*>(apkHandle);
+ if (zipFile == nullptr) {
+ ALOGE("zipfile handle is null");
+ return PAGE_SIZE_APP_COMPAT_FLAG_ERROR;
+ }
+
+ auto result = NativeLibrariesIterator::create(zipFile, debuggable);
+ if (!result.ok()) {
+ ALOGE("Can't iterate over native libs for file:%s", zipFile->getZipFileName());
+ return PAGE_SIZE_APP_COMPAT_FLAG_ERROR;
+ }
+ std::unique_ptr<NativeLibrariesIterator> it(std::move(result.value()));
+
+ const ScopedUtfChars cpuAbi(env, javaCpuAbi);
+ if (cpuAbi.c_str() == nullptr) {
+ ALOGE("cpuAbi is nullptr");
+ // This would've thrown, so this return code isn't observable by Java.
+ return PAGE_SIZE_APP_COMPAT_FLAG_ERROR;
+ }
+
+ while (true) {
+ auto next = it->next();
+ if (!next.ok()) {
+ ALOGE("next iterator not found Error:%d", next.error());
+ return PAGE_SIZE_APP_COMPAT_FLAG_ERROR;
+ }
+ auto entry = next.value();
+ if (entry == nullptr) {
+ break;
+ }
+
+ const char* fileName = it->currentEntry();
+ const char* lastSlash = it->lastSlash();
+
+ // Check to make sure the CPU ABI of this file is one we support.
+ const char* cpuAbiOffset = fileName + APK_LIB_LEN;
+ const size_t cpuAbiRegionSize = lastSlash - cpuAbiOffset;
+
+ if (cpuAbi.size() == cpuAbiRegionSize &&
+ !strncmp(cpuAbiOffset, cpuAbi.c_str(), cpuAbiRegionSize)) {
+ int ret = checkAlignment(env, javaNativeLibPath, extractNativeLibs, zipFile, entry,
+ lastSlash + 1);
+ if (ret == PAGE_SIZE_APP_COMPAT_FLAG_ERROR) {
+ ALOGE("Alignment check returned for zipfile: %s, entry:%s",
+ zipFile->getZipFileName(), lastSlash + 1);
+ return PAGE_SIZE_APP_COMPAT_FLAG_ERROR;
+ }
+ mode |= ret;
+ }
+ }
+
+ return mode;
+}
+
static void
com_android_internal_content_NativeLibraryHelper_close(JNIEnv *env, jclass, jlong apkHandle)
{
@@ -633,29 +797,23 @@ com_android_internal_content_NativeLibraryHelper_close(JNIEnv *env, jclass, jlon
}
static const JNINativeMethod gMethods[] = {
- {"nativeOpenApk",
- "(Ljava/lang/String;)J",
- (void *)com_android_internal_content_NativeLibraryHelper_openApk},
- {"nativeOpenApkFd",
- "(Ljava/io/FileDescriptor;Ljava/lang/String;)J",
- (void *)com_android_internal_content_NativeLibraryHelper_openApkFd},
- {"nativeClose",
- "(J)V",
- (void *)com_android_internal_content_NativeLibraryHelper_close},
- {"nativeCopyNativeBinaries",
- "(JLjava/lang/String;Ljava/lang/String;ZZ)I",
- (void *)com_android_internal_content_NativeLibraryHelper_copyNativeBinaries},
- {"nativeSumNativeBinaries",
- "(JLjava/lang/String;Z)J",
- (void *)com_android_internal_content_NativeLibraryHelper_sumNativeBinaries},
- {"nativeFindSupportedAbi",
- "(J[Ljava/lang/String;Z)I",
- (void *)com_android_internal_content_NativeLibraryHelper_findSupportedAbi},
- {"hasRenderscriptBitcode", "(J)I",
- (void *)com_android_internal_content_NativeLibraryHelper_hasRenderscriptBitcode},
+ {"nativeOpenApk", "(Ljava/lang/String;)J",
+ (void*)com_android_internal_content_NativeLibraryHelper_openApk},
+ {"nativeOpenApkFd", "(Ljava/io/FileDescriptor;Ljava/lang/String;)J",
+ (void*)com_android_internal_content_NativeLibraryHelper_openApkFd},
+ {"nativeClose", "(J)V", (void*)com_android_internal_content_NativeLibraryHelper_close},
+ {"nativeCopyNativeBinaries", "(JLjava/lang/String;Ljava/lang/String;ZZ)I",
+ (void*)com_android_internal_content_NativeLibraryHelper_copyNativeBinaries},
+ {"nativeSumNativeBinaries", "(JLjava/lang/String;Z)J",
+ (void*)com_android_internal_content_NativeLibraryHelper_sumNativeBinaries},
+ {"nativeFindSupportedAbi", "(J[Ljava/lang/String;Z)I",
+ (void*)com_android_internal_content_NativeLibraryHelper_findSupportedAbi},
+ {"hasRenderscriptBitcode", "(J)I",
+ (void*)com_android_internal_content_NativeLibraryHelper_hasRenderscriptBitcode},
+ {"nativeCheckAlignment", "(JLjava/lang/String;Ljava/lang/String;ZZ)I",
+ (void*)com_android_internal_content_NativeLibraryHelper_checkApkAlignment},
};
-
int register_com_android_internal_content_NativeLibraryHelper(JNIEnv *env)
{
return RegisterMethodsOrDie(env,
diff --git a/core/proto/android/content/package_item_info.proto b/core/proto/android/content/package_item_info.proto
index b7408a4da381..facadeedd1f8 100644
--- a/core/proto/android/content/package_item_info.proto
+++ b/core/proto/android/content/package_item_info.proto
@@ -114,6 +114,7 @@ message ApplicationInfoProto {
optional int32 enable_memtag = 20;
optional bool native_heap_zero_init = 21;
optional bool allow_cross_uid_activity_switch_from_below = 22;
+ optional int32 enable_page_size_app_compat = 23;
}
optional Detail detail = 17;
repeated string overlay_paths = 18;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index a13e334e28c8..e2f3d2a32d0b 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -848,10 +848,10 @@
<protected-broadcast android:name="android.app.action.CONSOLIDATED_NOTIFICATION_POLICY_CHANGED" />
<protected-broadcast android:name="android.intent.action.MAIN_USER_LOCKSCREEN_KNOWLEDGE_FACTOR_CHANGED" />
<protected-broadcast android:name="com.android.uwb.uwbcountrycode.GEOCODE_RETRY" />
- <protected-broadcast android:name="android.telephony.action.ACTION_SATELLITE_SUBSCRIBER_ID_LIST_CHANGED" />
+ <protected-broadcast android:name="android.telephony.satellite.action.SATELLITE_SUBSCRIBER_ID_LIST_CHANGED" />
<protected-broadcast android:name="android.service.ondeviceintelligence.MODEL_LOADED" />
<protected-broadcast android:name="android.service.ondeviceintelligence.MODEL_UNLOADED" />
- <protected-broadcast android:name="android.telephony.action.ACTION_SATELLITE_START_NON_EMERGENCY_SESSION" />
+ <protected-broadcast android:name="android.telephony.satellite.action.SATELLITE_START_NON_EMERGENCY_SESSION" />
<!-- ====================================================================== -->
@@ -6516,7 +6516,7 @@
@hide -->
<permission android:name="android.permission.BYPASS_CONCURRENT_RECORD_AUDIO_RESTRICTION"
android:featureFlag="android.media.audio.concurrent_audio_record_bypass_permission"
- android:protectionLevel="signature|privileged|role" />
+ android:protectionLevel="signature|privileged" />
<!-- @SystemApi Allows an application to capture audio for hotword detection.
<p>Not for use by third-party applications.</p>
@@ -9351,6 +9351,17 @@
</intent-filter>
</service>
+ <service android:name="com.android.ecm.EnhancedConfirmationCallTrackerService"
+ android:permission="android.permission.BIND_INCALL_SERVICE"
+ android:featureFlag="android.permission.flags.enhanced_confirmation_in_call_apis_enabled"
+ android:exported="true">
+ <meta-data android:name="android.telecom.INCLUDE_SELF_MANAGED_CALLS"
+ android:value="true" />
+ <intent-filter>
+ <action android:name="android.telecom.InCallService"/>
+ </intent-filter>
+ </service>
+
<service android:name="com.android.server.companion.datatransfer.contextsync.CallMetadataSyncConnectionService"
android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"
android:exported="true">
diff --git a/core/res/res/drawable-watch-v36/dialog_alert_button_background_negative.xml b/core/res/res/drawable-watch-v36/dialog_alert_button_background_negative.xml
index b6b8eac3a547..0314bbec2b55 100644
--- a/core/res/res/drawable-watch-v36/dialog_alert_button_background_negative.xml
+++ b/core/res/res/drawable-watch-v36/dialog_alert_button_background_negative.xml
@@ -18,7 +18,7 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/btn_material_filled_tonal_background_color"/>
- <corners android:radius="@dimen/config_bottomDialogCornerRadius" />
+ <corners android:radius="@dimen/config_wearMaterial3_bottomDialogCornerRadius" />
<size
android:width="@dimen/dialog_btn_negative_width"
android:height="@dimen/dialog_btn_negative_height" />
diff --git a/core/res/res/layout-watch-v36/alert_dialog_icon_button_wear_material3.xml b/core/res/res/layout-watch-v36/alert_dialog_icon_button_wear_material3.xml
new file mode 100644
index 000000000000..407ec7a42740
--- /dev/null
+++ b/core/res/res/layout-watch-v36/alert_dialog_icon_button_wear_material3.xml
@@ -0,0 +1,123 @@
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<!-- This layout is the AlertDialog template. It overrides the system layout with the same name.
+ Make sure to include all the existing id of the overridden alert_dialog_material.-->
+<com.android.internal.widget.WatchListDecorLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/parentPanel"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <ScrollView
+ android:id="@+id/scrollView"
+ android:fillViewport="true"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+ <!-- Top Panel -->
+ <FrameLayout
+ android:paddingLeft="?dialogPreferredPadding"
+ android:paddingRight="?dialogPreferredPadding"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:id="@+id/topPanel"
+ android:minHeight="@dimen/dialog_list_padding_top_no_title">
+ <include android:id="@+id/title_template"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ layout="@layout/alert_dialog_title_material"/>
+ </FrameLayout>
+
+ <!-- Content Panel -->
+ <FrameLayout android:id="@+id/contentPanel"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clipToPadding="false">
+ <TextView android:id="@+id/message"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal|top"
+ android:textAppearance="@style/TextAppearance.DeviceDefault.Body1"
+ android:paddingStart="?dialogPreferredPadding"
+ android:paddingEnd="?dialogPreferredPadding"
+ android:paddingTop="8dip"
+ android:paddingBottom="8dip"/>
+ </FrameLayout>
+
+ <!-- Custom Panel, to replace content panel if needed -->
+ <FrameLayout android:id="@+id/customPanel"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:minHeight="64dp">
+ <FrameLayout android:id="@+android:id/custom"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+ </FrameLayout>
+
+ <!-- Button Panel -->
+ <FrameLayout
+ android:id="@+id/buttonPanel"
+ android:minHeight="@dimen/dialog_list_padding_bottom_no_buttons"
+ android:layout_weight="1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center">
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom"
+ android:orientation="horizontal"
+ android:paddingBottom="?dialogPreferredPadding"
+ style="?android:attr/buttonBarStyle"
+ android:measureWithLargestChild="true">
+ <Button android:id="@+id/button2"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:gravity="center"
+ android:layout_weight="1"
+ style="@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog.WearMaterial3.Negative" />
+ <Button android:id="@+id/button3"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:gravity="center"
+ android:layout_weight="1"
+ style="?android:attr/buttonBarButtonStyle"/>
+ <FrameLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+ <Button android:id="@+id/button1"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center"
+ android:gravity="center"
+ android:layout_weight="1"
+ style="@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog.WearMaterial3.Confirm" />
+ <!-- This works as background. -->
+ <ImageView
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:src="@drawable/dialog_alert_button_positive"/>
+ </FrameLayout>
+ </LinearLayout>
+ </FrameLayout>
+ </LinearLayout>
+ </ScrollView>
+</com.android.internal.widget.WatchListDecorLayout>
diff --git a/core/res/res/layout-watch-v36/alert_dialog_material.xml b/core/res/res/layout-watch-v36/alert_dialog_material.xml
index 900102f379d9..8f7545690142 100644
--- a/core/res/res/layout-watch-v36/alert_dialog_material.xml
+++ b/core/res/res/layout-watch-v36/alert_dialog_material.xml
@@ -82,9 +82,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
- android:orientation="horizontal"
+ android:orientation="vertical"
android:paddingBottom="?dialogPreferredPadding"
- style="?android:attr/buttonBarStyle"
android:measureWithLargestChild="true">
<Button android:id="@+id/button2"
android:layout_width="wrap_content"
@@ -92,7 +91,7 @@
android:layout_gravity="center"
android:gravity="center"
android:layout_weight="1"
- style="@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog.Negative" />
+ style="@*android:style/Widget.DeviceDefault.Button.WearMaterial3"/>
<Button android:id="@+id/button3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -100,22 +99,13 @@
android:gravity="center"
android:layout_weight="1"
style="?android:attr/buttonBarButtonStyle"/>
- <FrameLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content">
- <Button android:id="@+id/button1"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_gravity="center"
- android:gravity="center"
- android:layout_weight="1"
- style="@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog.Confirm"/>
- <!-- This works as background. -->
- <ImageView
+ <Button android:id="@+id/button1"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:src="@drawable/dialog_alert_button_positive"/>
- </FrameLayout>
+ android:layout_gravity="center"
+ android:gravity="center"
+ android:layout_weight="1"
+ style="@*android:style/Widget.DeviceDefault.Button.Filled"/>
</LinearLayout>
</FrameLayout>
</LinearLayout>
diff --git a/core/res/res/layout/notification_2025_template_collapsed_base.xml b/core/res/res/layout/notification_2025_template_collapsed_base.xml
index 4eac6d991272..a790e5da20de 100644
--- a/core/res/res/layout/notification_2025_template_collapsed_base.xml
+++ b/core/res/res/layout/notification_2025_template_collapsed_base.xml
@@ -123,7 +123,7 @@
<!-- This is the simplest way to keep this text vertically centered without
gravity="center_vertical" which causes jumpiness in expansion animations. -->
<include
- layout="@layout/notification_2025_template_text"
+ layout="@layout/notification_2025_text"
android:layout_width="match_parent"
android:layout_height="@dimen/notification_text_height"
android:layout_gravity="center_vertical"
diff --git a/core/res/res/layout/notification_2025_template_collapsed_media.xml b/core/res/res/layout/notification_2025_template_collapsed_media.xml
new file mode 100644
index 000000000000..427c4e42e40c
--- /dev/null
+++ b/core/res/res/layout/notification_2025_template_collapsed_media.xml
@@ -0,0 +1,197 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2014 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<!-- Note: This is the old media style notification (different from UMO). -->
+
+<!-- extends FrameLayout -->
+<com.android.internal.widget.MediaNotificationView
+ android:id="@+id/status_bar_latest_event_content"
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/notification_min_height"
+ android:tag="media"
+ >
+
+
+ <ImageView
+ android:id="@+id/left_icon"
+ android:layout_width="@dimen/notification_2025_left_icon_size"
+ android:layout_height="@dimen/notification_2025_left_icon_size"
+ android:layout_gravity="center_vertical|start"
+ android:layout_marginStart="@dimen/notification_left_icon_start"
+ android:background="@drawable/notification_large_icon_outline"
+ android:clipToOutline="true"
+ android:importantForAccessibility="no"
+ android:scaleType="centerCrop"
+ android:visibility="gone"
+ />
+
+ <com.android.internal.widget.NotificationRowIconView
+ android:id="@+id/icon"
+ android:layout_width="@dimen/notification_2025_icon_circle_size"
+ android:layout_height="@dimen/notification_2025_icon_circle_size"
+ android:layout_gravity="center_vertical|start"
+ android:layout_marginStart="@dimen/notification_icon_circle_start"
+ android:background="@drawable/notification_icon_circle"
+ android:padding="@dimen/notification_2025_icon_circle_padding"
+ />
+
+ <FrameLayout
+ android:id="@+id/alternate_expand_target"
+ android:layout_width="@dimen/notification_2025_content_margin_start"
+ android:layout_height="match_parent"
+ android:layout_gravity="start"
+ android:importantForAccessibility="no"
+ android:focusable="false"
+ />
+
+ <LinearLayout
+ android:id="@+id/notification_headerless_view_row"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginStart="@dimen/notification_2025_content_margin_start"
+ android:orientation="horizontal"
+ >
+
+ <LinearLayout
+ android:id="@+id/notification_headerless_view_column"
+ android:layout_width="0px"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_weight="1"
+ android:layout_marginBottom="@dimen/notification_headerless_margin_twoline"
+ android:layout_marginTop="@dimen/notification_headerless_margin_twoline"
+ android:orientation="vertical"
+ >
+
+ <NotificationTopLineView
+ android:id="@+id/notification_top_line"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/notification_headerless_line_height"
+ android:clipChildren="false"
+ android:theme="@style/Theme.DeviceDefault.Notification"
+ >
+
+ <!--
+ NOTE: The notification_top_line_views layout contains the app_name_text.
+ In order to include the title view at the beginning, the Notification.Builder
+ has logic to hide that view whenever this title view is to be visible.
+ -->
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="@dimen/notification_header_separating_margin"
+ android:ellipsize="end"
+ android:fadingEdge="horizontal"
+ android:singleLine="true"
+ android:textAlignment="viewStart"
+ android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
+ />
+
+ <include layout="@layout/notification_top_line_views" />
+
+ </NotificationTopLineView>
+
+ <LinearLayout
+ android:id="@+id/notification_main_column"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ >
+
+ <com.android.internal.widget.NotificationVanishingFrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/notification_headerless_line_height"
+ >
+ <!-- This is the simplest way to keep this text vertically centered without
+ gravity="center_vertical" which causes jumpiness in expansion animations. -->
+ <include
+ layout="@layout/notification_template_text"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/notification_text_height"
+ android:layout_gravity="center_vertical"
+ android:layout_marginTop="0dp"
+ />
+ </com.android.internal.widget.NotificationVanishingFrameLayout>
+
+ <include
+ layout="@layout/notification_template_progress"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/notification_headerless_line_height"
+ />
+
+ </LinearLayout>
+
+ </LinearLayout>
+
+ <ImageView
+ android:id="@+id/right_icon"
+ android:layout_width="@dimen/notification_right_icon_size"
+ android:layout_height="@dimen/notification_right_icon_size"
+ android:layout_gravity="center_vertical|end"
+ android:layout_marginTop="@dimen/notification_right_icon_headerless_margin"
+ android:layout_marginBottom="@dimen/notification_right_icon_headerless_margin"
+ android:layout_marginStart="@dimen/notification_right_icon_content_margin"
+ android:background="@drawable/notification_large_icon_outline"
+ android:clipToOutline="true"
+ android:importantForAccessibility="no"
+ android:scaleType="centerCrop"
+ />
+
+ <LinearLayout
+ android:id="@+id/media_actions"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layoutDirection="ltr"
+ android:orientation="horizontal"
+ >
+ <include
+ layout="@layout/notification_material_media_action"
+ android:id="@+id/action0"
+ />
+ <include
+ layout="@layout/notification_material_media_action"
+ android:id="@+id/action1"
+ />
+ <include
+ layout="@layout/notification_material_media_action"
+ android:id="@+id/action2"
+ />
+ </LinearLayout>
+
+ <FrameLayout
+ android:id="@+id/expand_button_touch_container"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:minWidth="@dimen/notification_content_margin_end"
+ >
+
+ <include layout="@layout/notification_expand_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical|end"
+ />
+
+ </FrameLayout>
+
+ </LinearLayout>
+</com.android.internal.widget.MediaNotificationView>
diff --git a/core/res/res/layout/notification_2025_template_collapsed_messaging.xml b/core/res/res/layout/notification_2025_template_collapsed_messaging.xml
new file mode 100644
index 000000000000..f0e4c0f272fb
--- /dev/null
+++ b/core/res/res/layout/notification_2025_template_collapsed_messaging.xml
@@ -0,0 +1,220 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2016 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<!-- Note: This is the old "Messaging Style" notification (not a conversation). -->
+
+<!-- extends FrameLayout -->
+<com.android.internal.widget.MessagingLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/status_bar_latest_event_content"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clipChildren="false"
+ android:tag="messaging"
+ >
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clipChildren="false"
+ android:orientation="vertical"
+ >
+
+
+ <com.android.internal.widget.NotificationMaxHeightFrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/notification_min_height"
+ android:clipChildren="false"
+ >
+
+ <ImageView
+ android:id="@+id/left_icon"
+ android:layout_width="@dimen/notification_2025_left_icon_size"
+ android:layout_height="@dimen/notification_2025_left_icon_size"
+ android:layout_gravity="center_vertical|start"
+ android:layout_marginStart="@dimen/notification_left_icon_start"
+ android:background="@drawable/notification_large_icon_outline"
+ android:clipToOutline="true"
+ android:importantForAccessibility="no"
+ android:scaleType="centerCrop"
+ android:visibility="gone"
+ />
+
+ <com.android.internal.widget.NotificationRowIconView
+ android:id="@+id/icon"
+ android:layout_width="@dimen/notification_2025_icon_circle_size"
+ android:layout_height="@dimen/notification_2025_icon_circle_size"
+ android:layout_gravity="center_vertical|start"
+ android:layout_marginStart="@dimen/notification_icon_circle_start"
+ android:background="@drawable/notification_icon_circle"
+ android:padding="@dimen/notification_2025_icon_circle_padding"
+ />
+
+ <FrameLayout
+ android:id="@+id/alternate_expand_target"
+ android:layout_width="@dimen/notification_2025_content_margin_start"
+ android:layout_height="match_parent"
+ android:layout_gravity="start"
+ android:importantForAccessibility="no"
+ android:focusable="false"
+ />
+
+ <!--
+ NOTE: to make the expansion animation of id/notification_messaging happen vertically,
+ its X positioning must be the left edge of the notification, so instead of putting the
+ layout_marginStart on the id/notification_headerless_view_row, we put it on
+ id/notification_top_line, making the layout here just a bit different from the base.
+ -->
+ <LinearLayout
+ android:id="@+id/notification_headerless_view_row"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="horizontal"
+ android:clipChildren="false"
+ >
+
+ <!--
+ NOTE: because messaging will always have 2 lines, this LinearLayout should NOT
+ have the id/notification_headerless_view_column, as that is used for modifying
+ vertical margins to accommodate the single-line state that base supports
+ -->
+ <LinearLayout
+ android:layout_width="0px"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_weight="1"
+ android:layout_marginBottom="@dimen/notification_headerless_margin_twoline"
+ android:layout_marginTop="@dimen/notification_headerless_margin_twoline"
+ android:layout_marginStart="@dimen/notification_2025_content_margin_start"
+ android:clipChildren="false"
+ android:orientation="vertical"
+ >
+
+ <NotificationTopLineView
+ android:id="@+id/notification_top_line"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/notification_headerless_line_height"
+ android:clipChildren="false"
+ android:theme="@style/Theme.DeviceDefault.Notification"
+ >
+
+ <!--
+ NOTE: The notification_top_line_views layout contains the app_name_text.
+ In order to include the title view at the beginning, the Notification.Builder
+ has logic to hide that view whenever this title view is to be visible.
+ -->
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="@dimen/notification_header_separating_margin"
+ android:ellipsize="end"
+ android:fadingEdge="horizontal"
+ android:singleLine="true"
+ android:textAlignment="viewStart"
+ android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
+ />
+
+ <include layout="@layout/notification_top_line_views" />
+
+ </NotificationTopLineView>
+
+ <LinearLayout
+ android:id="@+id/notification_main_column"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:clipChildren="false"
+ >
+ <com.android.internal.widget.MessagingLinearLayout
+ android:id="@+id/notification_messaging"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clipChildren="false"
+ android:spacing="@dimen/notification_messaging_spacing" />
+ </LinearLayout>
+
+ </LinearLayout>
+
+ <!-- Images -->
+ <com.android.internal.widget.MessagingLinearLayout
+ android:id="@+id/conversation_image_message_container"
+ android:layout_width="@dimen/notification_right_icon_size"
+ android:layout_height="@dimen/notification_right_icon_size"
+ android:layout_gravity="center_vertical|end"
+ android:layout_marginTop="@dimen/notification_right_icon_headerless_margin"
+ android:layout_marginBottom="@dimen/notification_right_icon_headerless_margin"
+ android:layout_marginStart="@dimen/notification_right_icon_content_margin"
+ android:forceHasOverlappingRendering="false"
+ android:spacing="0dp"
+ android:clipChildren="false"
+ android:visibility="gone"
+ />
+
+ <ImageView
+ android:id="@+id/right_icon"
+ android:layout_width="@dimen/notification_right_icon_size"
+ android:layout_height="@dimen/notification_right_icon_size"
+ android:layout_gravity="center_vertical|end"
+ android:layout_marginTop="@dimen/notification_right_icon_headerless_margin"
+ android:layout_marginBottom="@dimen/notification_right_icon_headerless_margin"
+ android:layout_marginStart="@dimen/notification_right_icon_content_margin"
+ android:background="@drawable/notification_large_icon_outline"
+ android:clipToOutline="true"
+ android:importantForAccessibility="no"
+ android:scaleType="centerCrop"
+ />
+
+ <FrameLayout
+ android:id="@+id/expand_button_touch_container"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:minWidth="@dimen/notification_content_margin_end"
+ >
+
+ <include layout="@layout/notification_expand_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical|end"
+ />
+
+ </FrameLayout>
+
+ </LinearLayout>
+
+ </com.android.internal.widget.NotificationMaxHeightFrameLayout>
+
+ <LinearLayout
+ android:id="@+id/notification_action_list_margin_target"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="-20dp"
+ android:clipChildren="false"
+ android:orientation="vertical">
+ <include layout="@layout/notification_template_smart_reply_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/notification_content_margin"
+ android:layout_marginStart="@dimen/notification_2025_content_margin_start"
+ android:layout_marginEnd="@dimen/notification_content_margin_end" />
+ <include layout="@layout/notification_material_action_list" />
+ </LinearLayout>
+</LinearLayout>
+</com.android.internal.widget.MessagingLayout>
diff --git a/core/res/res/layout/notification_2025_template_text.xml b/core/res/res/layout/notification_2025_text.xml
index 48b1083a5e53..48b1083a5e53 100644
--- a/core/res/res/layout/notification_2025_template_text.xml
+++ b/core/res/res/layout/notification_2025_text.xml
diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml
index 565d584875ac..57959361bd48 100644
--- a/core/res/res/layout/notification_template_header.xml
+++ b/core/res/res/layout/notification_template_header.xml
@@ -13,7 +13,7 @@
~ See the License for the specific language governing permissions and
~ limitations under the License
-->
-<!-- extends FrameLayout -->
+<!-- extends RelativeLayout -->
<NotificationHeaderView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/notification_header"
@@ -62,7 +62,7 @@
android:layout_height="match_parent"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
- android:layout_toStartOf="@id/notification_buttons_column"
+ android:layout_toStartOf="@id/expand_button"
android:layout_alignWithParentIfMissing="true"
android:clipChildren="false"
android:gravity="center_vertical"
@@ -83,28 +83,17 @@
android:focusable="false"
/>
- <LinearLayout
- android:id="@+id/notification_buttons_column"
+ <include layout="@layout/notification_expand_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_alignParentEnd="true"
- android:orientation="vertical"
- >
-
- <include layout="@layout/notification_close_button"
- android:layout_width="@dimen/notification_close_button_size"
- android:layout_height="@dimen/notification_close_button_size"
- android:layout_gravity="end"
- android:layout_marginEnd="20dp"
- />
-
- <include layout="@layout/notification_expand_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentEnd="true"
- android:layout_centerVertical="true"
- />
+ android:layout_centerVertical="true"
+ android:layout_alignParentEnd="true" />
- </LinearLayout>
+ <include layout="@layout/notification_close_button"
+ android:id="@+id/close_button"
+ android:layout_width="@dimen/notification_close_button_size"
+ android:layout_height="@dimen/notification_close_button_size"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentEnd="true" />
</NotificationHeaderView>
diff --git a/core/res/res/layout/notification_template_material_base.xml b/core/res/res/layout/notification_template_material_base.xml
index 29f14a4a93fc..227f84bb2eed 100644
--- a/core/res/res/layout/notification_template_material_base.xml
+++ b/core/res/res/layout/notification_template_material_base.xml
@@ -157,39 +157,27 @@
android:maxDrawableHeight="@dimen/notification_right_icon_size"
/>
- <LinearLayout
- android:id="@+id/notification_buttons_column"
+ <FrameLayout
+ android:id="@+id/expand_button_touch_container"
android:layout_width="wrap_content"
android:layout_height="match_parent"
- android:layout_alignParentEnd="true"
- android:orientation="vertical"
+ android:minWidth="@dimen/notification_content_margin_end"
>
- <include layout="@layout/notification_close_button"
- android:layout_width="@dimen/notification_close_button_size"
- android:layout_height="@dimen/notification_close_button_size"
- android:layout_gravity="end"
- android:layout_marginEnd="20dp"
- />
-
- <FrameLayout
- android:id="@+id/expand_button_touch_container"
+ <include layout="@layout/notification_expand_button"
android:layout_width="wrap_content"
- android:layout_height="0dp"
- android:layout_weight="1"
- android:minWidth="@dimen/notification_content_margin_end"
- >
-
- <include layout="@layout/notification_expand_button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical|end"
- />
-
- </FrameLayout>
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical|end"
+ />
- </LinearLayout>
+ </FrameLayout>
</LinearLayout>
+ <include layout="@layout/notification_close_button"
+ android:id="@+id/close_button"
+ android:layout_width="@dimen/notification_close_button_size"
+ android:layout_height="@dimen/notification_close_button_size"
+ android:layout_gravity="top|end" />
+
</FrameLayout>
diff --git a/core/res/res/layout/notification_template_material_media.xml b/core/res/res/layout/notification_template_material_media.xml
index 6e9d17fc31d4..5459fa895f82 100644
--- a/core/res/res/layout/notification_template_material_media.xml
+++ b/core/res/res/layout/notification_template_material_media.xml
@@ -15,6 +15,7 @@
~ limitations under the License
-->
+<!-- extends FrameLayout -->
<com.android.internal.widget.MediaNotificationView
android:id="@+id/status_bar_latest_event_content"
xmlns:android="http://schemas.android.com/apk/res/android"
@@ -191,4 +192,11 @@
</FrameLayout>
</LinearLayout>
+
+ <include layout="@layout/notification_close_button"
+ android:id="@+id/close_button"
+ android:layout_width="@dimen/notification_close_button_size"
+ android:layout_height="@dimen/notification_close_button_size"
+ android:layout_gravity="top|end" />
+
</com.android.internal.widget.MediaNotificationView>
diff --git a/core/res/res/layout/notification_template_material_messaging.xml b/core/res/res/layout/notification_template_material_messaging.xml
index 1eae41da1e81..2b3b7d873f4f 100644
--- a/core/res/res/layout/notification_template_material_messaging.xml
+++ b/core/res/res/layout/notification_template_material_messaging.xml
@@ -195,6 +195,12 @@
</LinearLayout>
+ <include layout="@layout/notification_close_button"
+ android:id="@+id/close_button"
+ android:layout_width="@dimen/notification_close_button_size"
+ android:layout_height="@dimen/notification_close_button_size"
+ android:layout_gravity="top|end" />
+
</com.android.internal.widget.NotificationMaxHeightFrameLayout>
<LinearLayout
diff --git a/core/res/res/values-watch-v36/config.xml b/core/res/res/values-watch-v36/config.xml
index 1143ae30fe9d..679dc709ec35 100644
--- a/core/res/res/values-watch-v36/config.xml
+++ b/core/res/res/values-watch-v36/config.xml
@@ -16,5 +16,5 @@
<resources>
<dimen name="config_wearMaterial3_buttonCornerRadius">26dp</dimen>
- <dimen name="config_bottomDialogCornerRadius">18dp</dimen>
+ <dimen name="config_wearMaterial3_bottomDialogCornerRadius">18dp</dimen>
</resources>
diff --git a/core/res/res/values-watch-v36/styles_material.xml b/core/res/res/values-watch-v36/styles_material.xml
index 00f3f092b768..7da7435930b1 100644
--- a/core/res/res/values-watch-v36/styles_material.xml
+++ b/core/res/res/values-watch-v36/styles_material.xml
@@ -57,7 +57,7 @@
</style>
<!-- AlertDialog Styles -->
- <style name="Widget.DeviceDefault.Button.ButtonBar.AlertDialog" parent="Widget.DeviceDefault.Button">
+ <style name="Widget.DeviceDefault.Button.ButtonBar.AlertDialog.WearMaterial3" parent="Widget.DeviceDefault.Button">
<item name="android:textSize">0sp</item>
<item name="android:gravity">center</item>
<item name="android:paddingStart">0dp</item>
@@ -65,14 +65,14 @@
<item name="android:drawablePadding">0dp</item>
</style>
- <style name="Widget.DeviceDefault.Button.ButtonBar.AlertDialog.Confirm" parent="Widget.DeviceDefault.Button.ButtonBar.AlertDialog">
+ <style name="Widget.DeviceDefault.Button.ButtonBar.AlertDialog.WearMaterial3.Confirm">
<!-- Use a ImageView as background -->
<item name="background">@android:color/transparent</item>
<item name="minWidth">@dimen/dialog_btn_confirm_width</item>
<item name="minHeight">@dimen/dialog_btn_confirm_height</item>
</style>
- <style name="Widget.DeviceDefault.Button.ButtonBar.AlertDialog.Negative" parent="Widget.DeviceDefault.Button.ButtonBar.AlertDialog">
+ <style name="Widget.DeviceDefault.Button.ButtonBar.AlertDialog.WearMaterial3.Negative">
<item name="background">@drawable/dialog_alert_button_negative</item>
<item name="minWidth">@dimen/dialog_btn_negative_width</item>
<item name="minHeight">@dimen/dialog_btn_negative_height</item>
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 7ef539492aa4..cdf184c9c944 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1867,8 +1867,12 @@
16 KB device. 4 KB natives libs will be loaded app-compat mode if they are eligible.
@FlaggedApi(android.content.pm.Flags.FLAG_APP_COMPAT_OPTION_16KB) -->
<attr name="pageSizeCompat">
- <enum name="enabled" value="5" />
- <enum name="disabled" value="6" />
+ <!-- value for enabled must match with
+ ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_MANIFEST_OVERRIDE_ENABLED -->
+ <enum name="enabled" value="32" />
+ <!-- value for disabled must match with
+ ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_MANIFEST_OVERRIDE_DISABLED -->
+ <enum name="disabled" value="64" />
</attr>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 38ebda733270..73d696ba3567 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -6003,6 +6003,12 @@
<!-- If true, show multiuser switcher by default unless the user specifically disables it. -->
<bool name="config_showUserSwitcherByDefault">false</bool>
+ <!-- If true, user can change state of multiuser switcher. -->
+ <bool name="config_allowChangeUserSwitcherEnabled">true</bool>
+
+ <!-- If true, multiuser switcher would be automatically enabled when second user is created on the device. -->
+ <bool name="config_enableUserSwitcherUponUserCreation">true</bool>
+
<!-- Set to true to make assistant show in front of the dream/screensaver. -->
<bool name="config_assistantOnTopOfDream">false</bool>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 413f0c3e0c58..d498b9191559 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -199,6 +199,21 @@
<!-- Displayed to confirm to the user that caller ID will not be restricted on the next call or in general. -->
<string name="CLIRDefaultOffNextCallOff">Caller ID defaults to not restricted. Next call: Not restricted</string>
+ <!-- Message displayed in dialog when APK is not 16 KB aligned. [CHAR LIMIT=NONE] -->
+ <string name="page_size_compat_apk_warning">This app isn’t 16 KB compatible. APK alignment check failed.
+ This app will be run using page size compatible mode. For best compatibility, please recompile the application with 16 KB support.
+ For more information, see &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt; </string>
+
+ <!-- Message displayed in dialog when ELF is not 16 KB aligned. [CHAR LIMIT=NONE] -->
+ <string name="page_size_compat_elf_warning">This app isn’t 16 KB compatible. ELF alignment check failed.
+ This app will be run using page size compatible mode. For best compatibility, please recompile the application with 16 KB support.
+ For more information, see &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;</string>
+
+ <!-- Message displayed in dialog when APK and ELF are not 16 KB aligned. [CHAR LIMIT=NONE] -->
+ <string name="page_size_compat_apk_and_elf_warning">This app isn’t 16 KB compatible. APK and ELF alignment checks failed.
+ This app will be run using page size compatible mode. For best compatibility, please recompile the application with 16 KB support.
+ For more information, see &lt;a href=\"https://developer.android.com/16kb-page-size\"&gt;https://developer.android.com/16kb-page-size&lt;/a&gt;</string>
+
<!-- Displayed to tell the user that caller ID is not provisioned for their SIM. -->
<string name="serviceNotProvisioned">Service not provisioned.</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 94ffb48e28c1..2f82011726ec 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2393,6 +2393,8 @@
<java-symbol type="layout" name="notification_2025_template_expanded_base" />
<java-symbol type="layout" name="notification_2025_template_heads_up_base" />
<java-symbol type="layout" name="notification_2025_template_header" />
+ <java-symbol type="layout" name="notification_2025_template_collapsed_messaging" />
+ <java-symbol type="layout" name="notification_2025_template_collapsed_media" />
<java-symbol type="layout" name="notification_template_material_base" />
<java-symbol type="layout" name="notification_template_material_heads_up_base" />
<java-symbol type="layout" name="notification_template_material_compact_heads_up_base" />
@@ -3310,6 +3312,11 @@
<java-symbol type="string" name="language_selection_title" />
<java-symbol type="string" name="search_language_hint" />
+ <!-- Strings for page size app compat dialog -->
+ <java-symbol type="string" name="page_size_compat_apk_warning" />
+ <java-symbol type="string" name="page_size_compat_elf_warning" />
+ <java-symbol type="string" name="page_size_compat_apk_and_elf_warning" />
+
<!-- Work profile unlaunchable app alert dialog-->
<java-symbol type="style" name="AlertDialogWithEmergencyButton"/>
<java-symbol type="string" name="work_mode_emergency_call_button" />
@@ -4695,6 +4702,8 @@
<!-- If true, show multiuser switcher by default unless the user specifically disables it. -->
<java-symbol type="bool" name="config_showUserSwitcherByDefault" />
+ <java-symbol type="bool" name="config_allowChangeUserSwitcherEnabled" />
+ <java-symbol type="bool" name="config_enableUserSwitcherUponUserCreation" />
<!-- Set to true to make assistant show in front of the dream/screensaver. -->
<java-symbol type="bool" name="config_assistantOnTopOfDream"/>
@@ -5111,7 +5120,6 @@
<java-symbol type="layout" name="notification_expand_button"/>
<java-symbol type="id" name="close_button" />
<java-symbol type="layout" name="notification_close_button"/>
- <java-symbol type="id" name="notification_buttons_column" />
<java-symbol type="bool" name="config_supportsMicToggle" />
<java-symbol type="bool" name="config_supportsCamToggle" />
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/drawable-mdpi/border_ltr.9.png b/core/tests/batterystatstests/BatteryStatsViewer/res/drawable-mdpi/border_ltr.9.png
new file mode 100644
index 000000000000..c7e937c6d77a
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/drawable-mdpi/border_ltr.9.png
Binary files differ
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/drawable-mdpi/border_tr.9.png b/core/tests/batterystatstests/BatteryStatsViewer/res/drawable-mdpi/border_tr.9.png
new file mode 100644
index 000000000000..a3cff989bad8
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/drawable-mdpi/border_tr.9.png
Binary files differ
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/power_other_24.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/power_other_24.xml
new file mode 100644
index 000000000000..c9fb53ee78da
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/power_other_24.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:tint="?attr/colorControlNormal"
+ android:viewportHeight="960"
+ android:viewportWidth="960">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M720,600L720,520L800,520Q817,520 828.5,531.5Q840,543 840,560Q840,577 828.5,588.5Q817,600 800,600L720,600ZM720,760L720,680L800,680Q817,680 828.5,691.5Q840,703 840,720Q840,737 828.5,748.5Q817,760 800,760L720,760ZM560,800Q527,800 503.5,776.5Q480,753 480,720L400,720L400,560L480,560Q480,527 503.5,503.5Q527,480 560,480L680,480L680,800L560,800ZM280,680Q214,680 167,633Q120,586 120,520Q120,454 167,407Q214,360 280,360L340,360Q365,360 382.5,342.5Q400,325 400,300Q400,275 382.5,257.5Q365,240 340,240L200,240Q183,240 171.5,228.5Q160,217 160,200Q160,183 171.5,171.5Q183,160 200,160L340,160Q398,160 439,201Q480,242 480,300Q480,358 439,399Q398,440 340,440L280,440Q247,440 223.5,463.5Q200,487 200,520Q200,553 223.5,576.5Q247,600 280,600L360,600L360,680L280,680Z" />
+</vector> \ No newline at end of file
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/screen_off_24.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/screen_off_24.xml
new file mode 100644
index 000000000000..ca9482503c92
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/screen_off_24.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:tint="?attr/colorControlNormal"
+ android:viewportHeight="960"
+ android:viewportWidth="960">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M280,920Q247,920 223.5,896.5Q200,873 200,840L200,120Q200,87 223.5,63.5Q247,40 280,40L680,40Q713,40 736.5,63.5Q760,87 760,120L760,840Q760,873 736.5,896.5Q713,920 680,920L280,920ZM280,800L280,840Q280,840 280,840Q280,840 280,840L680,840Q680,840 680,840Q680,840 680,840L680,800L280,800ZM280,720L680,240L280,720ZM280,160L680,160L680,120Q680,120 680,120Q680,120 680,120L280,120Q280,120 280,120Q280,120 280,120L280,160ZM280,160L280,120Q280,120 280,120Q280,120 280,120L280,120Q280,120 280,120Q280,120 280,120L280,160ZM280,800L280,800L280,840Q280,840 280,840Q280,840 280,840L280,840Q280,840 280,840Q280,840 280,840L280,800Z" />
+</vector> \ No newline at end of file
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/screen_on_24.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/screen_on_24.xml
new file mode 100644
index 000000000000..48f990c3ba37
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/screen_on_24.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:tint="?attr/colorControlNormal"
+ android:viewportHeight="960"
+ android:viewportWidth="960">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M280,920Q247,920 223.5,896.5Q200,873 200,840L200,120Q200,87 223.5,63.5Q247,40 280,40L680,40Q713,40 736.5,63.5Q760,87 760,120L760,840Q760,873 736.5,896.5Q713,920 680,920L280,920ZM280,800L280,840Q280,840 280,840Q280,840 280,840L680,840Q680,840 680,840Q680,840 680,840L680,800L280,800ZM280,720L680,720L680,240L280,240L280,720ZM280,160L680,160L680,120Q680,120 680,120Q680,120 680,120L280,120Q280,120 280,120Q280,120 280,120L280,160ZM280,160L280,120Q280,120 280,120Q280,120 280,120L280,120Q280,120 280,120Q280,120 280,120L280,160ZM280,800L280,800L280,840Q280,840 280,840Q280,840 280,840L280,840Q280,840 280,840Q280,840 280,840L280,800Z" />
+</vector> \ No newline at end of file
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_consumer_entry_layout.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_consumer_entry_layout.xml
index be0e135af23a..e1f46232488c 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_consumer_entry_layout.xml
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_consumer_entry_layout.xml
@@ -1,5 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
+<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright (C) 2020 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
@@ -14,46 +13,127 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:minHeight="?android:attr/listPreferredItemHeightSmall"
- android:paddingStart="?android:attr/listPreferredItemPaddingStart"
- android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
- android:paddingTop="8dp"
- android:paddingBottom="8dp">
-
- <ImageView
- android:id="@+id/icon"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- android:layout_marginEnd="8dp"
- android:paddingBottom="8dp"/>
-
- <TextView
- android:id="@+id/title"
- android:layout_width="0dp"
- android:layout_weight="1"
- android:layout_height="wrap_content"
- android:textAppearance="@style/TextAppearanceBody"/>
+ android:orientation="vertical">
- <TextView
- android:id="@+id/value1"
- android:layout_width="wrap_content"
+ <LinearLayout
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginStart="8dp"
- android:gravity="right"
- android:maxLines="1"
- android:textAppearance="@style/TextAppearanceBody"/>
-
- <TextView
- android:id="@+id/value2"
- android:layout_width="76dp"
+ android:minHeight="?android:attr/listPreferredItemHeightSmall"
+ android:orientation="horizontal"
+ android:paddingBottom="8dp"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingTop="8dp">
+
+ <ImageView
+ android:id="@+id/icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_marginEnd="8dp"
+ android:paddingBottom="8dp" />
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:textAppearance="@style/TextAppearanceBody" />
+
+ <TextView
+ android:id="@+id/value1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="8dp"
+ android:gravity="right"
+ android:maxLines="1"
+ android:textAppearance="@style/TextAppearanceBody" />
+
+ <TextView
+ android:id="@+id/value2"
+ android:layout_width="76dp"
+ android:layout_height="wrap_content"
+ android:gravity="right"
+ android:maxLines="1"
+ android:textAppearance="@style/TextAppearanceBody" />
+ </LinearLayout>
+
+ <TableLayout
+ android:id="@+id/table"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:gravity="right"
- android:maxLines="1"
- android:textAppearance="@style/TextAppearanceBody"/>
+ android:layout_marginEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:layout_marginStart="50dp"
+ android:stretchColumns="1,2,3,4">
+
+ <TableRow android:background="#EEFFEE">
+ <LinearLayout
+ style="@style/TableCell.Start"
+ android:layout_width="65dp">
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="3dip"
+ android:text="State"
+ android:textStyle="bold" />
+ </LinearLayout>
+
+ <RelativeLayout style="@style/TableCell.Inner">
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerInParent="true"
+ android:src="@drawable/screen_on_24"
+ android:tint="@color/battery_consumer_slice_icon" />
+ </RelativeLayout>
+
+ <RelativeLayout style="@style/TableCell.Inner">
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerInParent="true"
+ android:src="@drawable/screen_off_24"
+ android:tint="@color/battery_consumer_slice_icon" />
+ </RelativeLayout>
+
+ <RelativeLayout style="@style/TableCell.Inner">
+ <ImageView
+ android:id="@+id/screen_on_24_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerInParent="true"
+ android:src="@drawable/screen_on_24"
+ android:tint="@color/battery_consumer_slice_icon" />
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_toRightOf="@id/screen_on_24_icon"
+ android:src="@drawable/power_other_24"
+ android:tint="@color/battery_consumer_slice_icon" />
+ </RelativeLayout>
+
+ <RelativeLayout style="@style/TableCell.End">
+ <ImageView
+ android:id="@+id/screen_off_24_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerInParent="true"
+ android:src="@drawable/screen_off_24"
+ android:tint="@color/battery_consumer_slice_icon" />
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_toRightOf="@id/screen_off_24_icon"
+ android:src="@drawable/power_other_24"
+ android:tint="@color/battery_consumer_slice_icon" />
+ </RelativeLayout>
+ </TableRow>
+
+ <View
+ android:layout_height="1dip"
+ android:background="#000000" />
+ </TableLayout>
</LinearLayout>
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_consumer_picker_layout.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_consumer_picker_layout.xml
index 987de6bcb4f4..b88425a57187 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_consumer_picker_layout.xml
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_consumer_picker_layout.xml
@@ -14,16 +14,30 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/swipe_refresh"
- android:paddingTop="?attr/actionBarSize"
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
- android:layout_height="match_parent">
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:fitsSystemWindows="true">
- <androidx.recyclerview.widget.RecyclerView
- android:id="@+id/list_view"
+ <androidx.appcompat.widget.Toolbar
+ android:id="@+id/toolbar"
android:layout_width="match_parent"
- android:layout_height="match_parent"/>
+ android:layout_height="?attr/actionBarSize"
+ android:background="?attr/colorPrimary"
+ android:elevation="4dp"
+ android:theme="@style/ThemeOverlay.AppCompat.ActionBar" />
-</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
+ <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
+ android:id="@+id/swipe_refresh"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1">
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/list_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+ </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
+</LinearLayout>
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_consumer_slices_layout.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_consumer_slices_layout.xml
new file mode 100644
index 000000000000..642c0deac6f4
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_consumer_slices_layout.xml
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<TableRow xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <LinearLayout style="@style/TableCell.Start">
+ <TextView
+ android:id="@+id/procState"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+ </LinearLayout>
+
+ <LinearLayout
+ style="@style/TableCell.Inner"
+ android:orientation="vertical">
+ <TextView
+ android:id="@+id/power_b_on"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="right" />
+ <TextView
+ android:id="@+id/duration_b_on"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="right" />
+ </LinearLayout>
+
+ <LinearLayout
+ style="@style/TableCell.Inner"
+ android:orientation="vertical">
+ <TextView
+ android:id="@+id/power_b_off"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="right" />
+ <TextView
+ android:id="@+id/duration_b_off"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="right" />
+ </LinearLayout>
+
+ <LinearLayout
+ style="@style/TableCell.Inner"
+ android:orientation="vertical">
+ <TextView
+ android:id="@+id/power_c_on"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="right" />
+ <TextView
+ android:id="@+id/duration_c_on"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="right" />
+ </LinearLayout>
+
+ <LinearLayout
+ style="@style/TableCell.End"
+ android:orientation="vertical">
+ <TextView
+ android:id="@+id/power_c_off"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="right" />
+ <TextView
+ android:id="@+id/duration_c_off"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="right" />
+ </LinearLayout>
+</TableRow>
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_stats_viewer_layout.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_stats_viewer_layout.xml
index 2d276a51a1da..46d8f04f9eef 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_stats_viewer_layout.xml
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_stats_viewer_layout.xml
@@ -17,13 +17,12 @@
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/swipe_refresh"
- android:paddingTop="?attr/actionBarSize"
android:layout_width="match_parent"
- android:layout_height="match_parent">
+ android:layout_height="match_parent"
+ android:fitsSystemWindows="true">
<LinearLayout
android:orientation="vertical"
- android:paddingTop="?attr/actionBarSize"
android:layout_width="match_parent"
android:layout_height="match_parent">
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/values/colors.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/values/colors.xml
index 6cc70bd1af61..1dc288af89b6 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/res/values/colors.xml
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/values/colors.xml
@@ -18,4 +18,5 @@
<resources>
<color name="battery_consumer_bg_power_profile">#ffffff</color>
<color name="battery_consumer_bg_energy_consumption">#fff5eb</color>
+ <color name="battery_consumer_slice_icon">#aaaaaa</color>
</resources>
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/values/styles.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/values/styles.xml
index fa30b2c8dc6f..a298cc9f59e3 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/res/values/styles.xml
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/values/styles.xml
@@ -17,11 +17,9 @@
-->
<resources>
- <style name="Theme" parent="Theme.MaterialComponents.Light">
+ <style name="Theme" parent="Theme.MaterialComponents.Light.NoActionBar">
<item name="colorPrimary">#34a853</item>
- <item name="android:windowActionBar">true</item>
- <item name="android:windowNoTitle">false</item>
- <item name="android:windowDrawsSystemBarBackgrounds">false</item>
+ <item name="toolbarStyle">@style/Widget.AppCompat.Toolbar</item>
</style>
<style name="LoadTestCardView" parent="Widget.MaterialComponents.CardView">
@@ -32,4 +30,25 @@
<item name="android:textColor">#000000</item>
<item name="android:textSize">18sp</item>
</style>
-</resources> \ No newline at end of file
+
+ <style name="TableCell">
+ <item name="android:layout_height">match_parent</item>
+ <item name="android:padding">4dp</item>
+ </style>
+
+ <style name="TableCell.Start" parent="TableCell">
+ <item name="android:background">@drawable/border_ltr</item>
+ </style>
+
+ <style name="TableCell.Inner" parent="TableCell">
+ <item name="android:background">@drawable/border_tr</item>
+ <item name="android:layout_width">0dp</item>
+ <item name="android:layout_weight">1</item>
+ </style>
+
+ <style name="TableCell.End" parent="TableCell">
+ <item name="android:background">@drawable/border_tr</item>
+ <item name="android:layout_width">0dp</item>
+ <item name="android:layout_weight">1</item>
+ </style>
+</resources>
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerData.java b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerData.java
index f691a1b90934..35175a755e89 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerData.java
+++ b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerData.java
@@ -31,22 +31,16 @@ public class BatteryConsumerData {
public static final String UID_BATTERY_CONSUMER_ID_PREFIX = "APP|";
public static final String AGGREGATE_BATTERY_CONSUMER_ID = "SYS|";
- enum EntryType {
- UID_TOTAL_POWER,
- UID_POWER_PROFILE,
- UID_POWER_PROFILE_PROCESS_STATE,
- UID_POWER_ENERGY_CONSUMPTION,
- UID_POWER_ENERGY_PROCESS_STATE,
- UID_POWER_CUSTOM,
- UID_DURATION,
+ public enum EntryType {
DEVICE_TOTAL_POWER,
- DEVICE_POWER_MODELED,
+ DEVICE_POWER,
DEVICE_POWER_ENERGY_CONSUMPTION,
DEVICE_POWER_CUSTOM,
DEVICE_DURATION,
+ UID,
}
- enum ConsumerType {
+ public enum ConsumerType {
UID_BATTERY_CONSUMER,
DEVICE_POWER_COMPONENT,
}
@@ -56,34 +50,38 @@ public class BatteryConsumerData {
public String title;
public double value1;
public double value2;
+ public List<Slice> slices;
+ }
+
+ public static class Slice {
+ public int powerState;
+ public int screenState;
+ public int processState;
+ public double powerMah;
+ public long durationMs;
}
private BatteryConsumerInfoHelper.BatteryConsumerInfo mBatteryConsumerInfo;
private final List<Entry> mEntries = new ArrayList<>();
public BatteryConsumerData(Context context,
- List<BatteryUsageStats> batteryUsageStatsList, String batteryConsumerId) {
+ BatteryUsageStats batteryUsageStats, String batteryConsumerId) {
switch (getConsumerType(batteryConsumerId)) {
case UID_BATTERY_CONSUMER:
- populateForUidBatteryConsumer(context, batteryUsageStatsList, batteryConsumerId);
+ populateForUidBatteryConsumer(context, batteryUsageStats, batteryConsumerId);
break;
case DEVICE_POWER_COMPONENT:
- populateForAggregateBatteryConsumer(context, batteryUsageStatsList);
+ populateForAggregateBatteryConsumer(context, batteryUsageStats);
break;
}
}
- private void populateForUidBatteryConsumer(
- Context context, List<BatteryUsageStats> batteryUsageStatsList,
+ private void populateForUidBatteryConsumer(Context context, BatteryUsageStats batteryUsageStats,
String batteryConsumerId) {
- BatteryUsageStats batteryUsageStats = batteryUsageStatsList.get(0);
- BatteryUsageStats modeledBatteryUsageStats = batteryUsageStatsList.get(1);
BatteryConsumer requestedBatteryConsumer = getRequestedBatteryConsumer(batteryUsageStats,
batteryConsumerId);
- BatteryConsumer requestedModeledBatteryConsumer = getRequestedBatteryConsumer(
- modeledBatteryUsageStats, batteryConsumerId);
- if (requestedBatteryConsumer == null || requestedModeledBatteryConsumer == null) {
+ if (requestedBatteryConsumer == null) {
mBatteryConsumerInfo = null;
return;
}
@@ -92,118 +90,95 @@ public class BatteryConsumerData {
batteryUsageStats, batteryConsumerId, context.getPackageManager());
double[] totalPowerByComponentMah = new double[BatteryConsumer.POWER_COMPONENT_COUNT];
- double[] totalModeledPowerByComponentMah =
- new double[BatteryConsumer.POWER_COMPONENT_COUNT];
long[] totalDurationByComponentMs = new long[BatteryConsumer.POWER_COMPONENT_COUNT];
- final int customComponentCount =
- requestedBatteryConsumer.getCustomPowerComponentCount();
+ final int customComponentCount = requestedBatteryConsumer.getCustomPowerComponentCount();
double[] totalCustomPowerByComponentMah = new double[customComponentCount];
computeTotalPower(batteryUsageStats, totalPowerByComponentMah);
- computeTotalPower(modeledBatteryUsageStats, totalModeledPowerByComponentMah);
computeTotalPowerForCustomComponent(batteryUsageStats, totalCustomPowerByComponentMah);
computeTotalDuration(batteryUsageStats, totalDurationByComponentMs);
- if (isPowerProfileModelsOnly(requestedBatteryConsumer)) {
- addEntry("Consumed", EntryType.UID_TOTAL_POWER,
- requestedBatteryConsumer.getConsumedPower(),
- batteryUsageStats.getAggregateBatteryConsumer(
- BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
- .getConsumedPower());
- } else {
- addEntry("Consumed (PowerStats)", EntryType.UID_TOTAL_POWER,
- requestedBatteryConsumer.getConsumedPower(),
- batteryUsageStats.getAggregateBatteryConsumer(
- BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
- .getConsumedPower());
- addEntry("Consumed (PowerProfile)", EntryType.UID_TOTAL_POWER,
- requestedModeledBatteryConsumer.getConsumedPower(),
- modeledBatteryUsageStats.getAggregateBatteryConsumer(
- BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
- .getConsumedPower());
+ Entry totalsEntry = addEntry("Consumed", EntryType.UID,
+ requestedBatteryConsumer.getConsumedPower(),
+ batteryUsageStats.getAggregateBatteryConsumer(
+ BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
+ .getConsumedPower());
+ addSlices(totalsEntry, requestedBatteryConsumer, BatteryConsumer.POWER_COMPONENT_BASE);
+ for (Slice slice : totalsEntry.slices) {
+ slice.powerMah = requestedBatteryConsumer.getConsumedPower(
+ new BatteryConsumer.Dimensions(BatteryConsumer.POWER_COMPONENT_ANY,
+ slice.processState, slice.screenState, slice.powerState));
}
for (int component = 0; component < BatteryConsumer.POWER_COMPONENT_COUNT; component++) {
+ if (component == BatteryConsumer.POWER_COMPONENT_BASE) {
+ continue;
+ }
final String metricTitle = getPowerMetricTitle(component);
- final int powerModel = requestedBatteryConsumer.getPowerModel(component);
- if (powerModel == BatteryConsumer.POWER_MODEL_POWER_PROFILE
- || powerModel == BatteryConsumer.POWER_MODEL_UNDEFINED) {
- addEntry(metricTitle, EntryType.UID_POWER_PROFILE,
- requestedBatteryConsumer.getConsumedPower(component),
+ double consumedPower = requestedBatteryConsumer.getConsumedPower(component);
+ if (consumedPower != 0) {
+ Entry entry = addEntry(metricTitle, EntryType.UID, consumedPower,
totalPowerByComponentMah[component]);
- addProcessStateEntries(metricTitle, EntryType.UID_POWER_PROFILE_PROCESS_STATE,
- requestedBatteryConsumer, component);
- } else {
- addEntry(metricTitle + " (PowerStats)", EntryType.UID_POWER_ENERGY_CONSUMPTION,
- requestedBatteryConsumer.getConsumedPower(component),
- totalPowerByComponentMah[component]);
- addProcessStateEntries(metricTitle, EntryType.UID_POWER_ENERGY_PROCESS_STATE,
- requestedBatteryConsumer, component);
- addEntry(metricTitle + " (PowerProfile)", EntryType.UID_POWER_PROFILE,
- requestedModeledBatteryConsumer.getConsumedPower(component),
- totalModeledPowerByComponentMah[component]);
- addProcessStateEntries(metricTitle, EntryType.UID_POWER_PROFILE_PROCESS_STATE,
- requestedModeledBatteryConsumer, component);
+ addSlices(entry, requestedBatteryConsumer, component);
}
}
for (int component = 0; component < customComponentCount; component++) {
- final String name = requestedBatteryConsumer.getCustomPowerComponentName(
- BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + component);
- addEntry(name + " (PowerStats)", EntryType.UID_POWER_CUSTOM,
- requestedBatteryConsumer.getConsumedPowerForCustomComponent(
- BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + component),
- totalCustomPowerByComponentMah[component]
- );
- }
-
- for (int component = 0; component < BatteryConsumer.POWER_COMPONENT_COUNT; component++) {
- final String metricTitle = getTimeMetricTitle(component);
- addEntry(metricTitle, EntryType.UID_DURATION,
- requestedBatteryConsumer.getUsageDurationMillis(component),
- totalDurationByComponentMs[component]
- );
+ int componentId = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + component;
+ final String name = requestedBatteryConsumer.getCustomPowerComponentName(componentId);
+ double consumedPower = requestedBatteryConsumer.getConsumedPower(componentId);
+ if (consumedPower != 0) {
+ Entry entry = addEntry(name, EntryType.UID, consumedPower,
+ totalCustomPowerByComponentMah[component]);
+ addSlices(entry, requestedBatteryConsumer, componentId);
+ }
}
mBatteryConsumerInfo = BatteryConsumerInfoHelper.makeBatteryConsumerInfo(batteryUsageStats,
batteryConsumerId, context.getPackageManager());
}
- private void addProcessStateEntries(String metricTitle, EntryType entryType,
- BatteryConsumer batteryConsumer, int component) {
+ private void addSlices(Entry entry, BatteryConsumer batteryConsumer, int component) {
final BatteryConsumer.Key[] keys = batteryConsumer.getKeys(component);
if (keys == null || keys.length <= 1) {
return;
}
+ boolean hasProcStateData = false;
for (BatteryConsumer.Key key : keys) {
- String label;
- switch (key.processState) {
- case BatteryConsumer.PROCESS_STATE_FOREGROUND:
- label = "foreground";
- break;
- case BatteryConsumer.PROCESS_STATE_BACKGROUND:
- label = "background";
- break;
- case BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE:
- label = "FGS";
- break;
- case BatteryConsumer.PROCESS_STATE_CACHED:
- label = "cached";
- break;
- default:
- continue;
+ if (key.processState != BatteryConsumer.PROCESS_STATE_UNSPECIFIED) {
+ hasProcStateData = true;
+ break;
}
- addEntry(metricTitle + " \u2022 " + label, entryType,
- batteryConsumer.getConsumedPower(key), 0);
}
+
+ ArrayList<Slice> slices = new ArrayList<>();
+ for (BatteryConsumer.Key key : keys) {
+ if (hasProcStateData && key.processState == BatteryConsumer.PROCESS_STATE_UNSPECIFIED) {
+ continue;
+ }
+
+ double powerMah = batteryConsumer.getConsumedPower(key);
+ long durationMs = batteryConsumer.getUsageDurationMillis(key);
+
+ if (powerMah == 0 && durationMs == 0) {
+ continue;
+ }
+
+ Slice slice = new Slice();
+ slice.powerState = key.powerState;
+ slice.screenState = key.screenState;
+ slice.processState = key.processState;
+ slice.powerMah = powerMah;
+ slice.durationMs = durationMs;
+
+ slices.add(slice);
+ }
+ entry.slices = slices;
}
private void populateForAggregateBatteryConsumer(Context context,
- List<BatteryUsageStats> batteryUsageStatsList) {
- BatteryUsageStats batteryUsageStats = batteryUsageStatsList.get(0);
- BatteryUsageStats modeledBatteryUsageStats = batteryUsageStatsList.get(1);
-
+ BatteryUsageStats batteryUsageStats) {
final BatteryConsumer deviceBatteryConsumer =
batteryUsageStats.getAggregateBatteryConsumer(
BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE);
@@ -211,46 +186,18 @@ public class BatteryConsumerData {
batteryUsageStats.getAggregateBatteryConsumer(
BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS);
- BatteryConsumer modeledDeviceBatteryConsumer =
- modeledBatteryUsageStats.getAggregateBatteryConsumer(
- BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE);
- BatteryConsumer modeledAppsBatteryConsumer =
- modeledBatteryUsageStats.getAggregateBatteryConsumer(
- BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS);
-
- if (isPowerProfileModelsOnly(deviceBatteryConsumer)) {
- addEntry("Consumed", EntryType.DEVICE_TOTAL_POWER,
- deviceBatteryConsumer.getConsumedPower(),
- appsBatteryConsumer.getConsumedPower());
- } else {
- addEntry("Consumed (PowerStats)", EntryType.DEVICE_TOTAL_POWER,
- deviceBatteryConsumer.getConsumedPower(),
- appsBatteryConsumer.getConsumedPower());
- addEntry("Consumed (PowerProfile)", EntryType.DEVICE_TOTAL_POWER,
- modeledDeviceBatteryConsumer.getConsumedPower(),
- modeledAppsBatteryConsumer.getConsumedPower());
- }
+ addEntry("Consumed", EntryType.DEVICE_TOTAL_POWER,
+ deviceBatteryConsumer.getConsumedPower(),
+ appsBatteryConsumer.getConsumedPower());
mBatteryConsumerInfo = BatteryConsumerInfoHelper.makeBatteryConsumerInfo(batteryUsageStats,
AGGREGATE_BATTERY_CONSUMER_ID, context.getPackageManager());
-
for (int component = 0; component < BatteryConsumer.POWER_COMPONENT_COUNT; component++) {
final String metricTitle = getPowerMetricTitle(component);
- final int powerModel = deviceBatteryConsumer.getPowerModel(component);
- if (powerModel == BatteryConsumer.POWER_MODEL_POWER_PROFILE
- || powerModel == BatteryConsumer.POWER_MODEL_UNDEFINED) {
- addEntry(metricTitle, EntryType.DEVICE_POWER_MODELED,
- deviceBatteryConsumer.getConsumedPower(component),
- appsBatteryConsumer.getConsumedPower(component));
- } else {
- addEntry(metricTitle + " (PowerStats)", EntryType.DEVICE_POWER_ENERGY_CONSUMPTION,
- deviceBatteryConsumer.getConsumedPower(component),
- appsBatteryConsumer.getConsumedPower(component));
- addEntry(metricTitle + " (PowerProfile)", EntryType.DEVICE_POWER_MODELED,
- modeledDeviceBatteryConsumer.getConsumedPower(component),
- modeledAppsBatteryConsumer.getConsumedPower(component));
- }
+ addEntry(metricTitle, EntryType.DEVICE_POWER,
+ deviceBatteryConsumer.getConsumedPower(component),
+ appsBatteryConsumer.getConsumedPower(component));
}
final int customComponentCount =
@@ -258,10 +205,10 @@ public class BatteryConsumerData {
for (int component = 0; component < customComponentCount; component++) {
final String name = deviceBatteryConsumer.getCustomPowerComponentName(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + component);
- addEntry(name + " (PowerStats)", EntryType.DEVICE_POWER_CUSTOM,
- deviceBatteryConsumer.getConsumedPowerForCustomComponent(
+ addEntry(name, EntryType.DEVICE_POWER_CUSTOM,
+ deviceBatteryConsumer.getConsumedPower(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + component),
- appsBatteryConsumer.getConsumedPowerForCustomComponent(
+ appsBatteryConsumer.getConsumedPower(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + component));
}
@@ -272,17 +219,6 @@ public class BatteryConsumerData {
}
}
- private boolean isPowerProfileModelsOnly(BatteryConsumer batteryConsumer) {
- for (int component = 0; component < BatteryConsumer.POWER_COMPONENT_COUNT; component++) {
- final int powerModel = batteryConsumer.getPowerModel(component);
- if (powerModel != BatteryConsumer.POWER_MODEL_POWER_PROFILE
- && powerModel != BatteryConsumer.POWER_MODEL_UNDEFINED) {
- return false;
- }
- }
- return true;
- }
-
private BatteryConsumer getRequestedBatteryConsumer(BatteryUsageStats batteryUsageStats,
String batteryConsumerId) {
for (UidBatteryConsumer consumer : batteryUsageStats.getUidBatteryConsumers()) {
@@ -352,13 +288,14 @@ public class BatteryConsumerData {
}
}
- private void addEntry(String title, EntryType entryType, double value1, double value2) {
+ private Entry addEntry(String title, EntryType entryType, double value1, double value2) {
Entry entry = new Entry();
entry.title = title;
entry.entryType = entryType;
entry.value1 = value1;
entry.value2 = value2;
mEntries.add(entry);
+ return entry;
}
public BatteryConsumerInfoHelper.BatteryConsumerInfo getBatteryConsumerInfo() {
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerInfoHelper.java b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerInfoHelper.java
index c6d71c3f573a..37d6b17a665c 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerInfoHelper.java
+++ b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerInfoHelper.java
@@ -24,6 +24,8 @@ import android.os.UidBatteryConsumer;
import androidx.annotation.NonNull;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
import java.util.List;
class BatteryConsumerInfoHelper {
@@ -76,6 +78,8 @@ class BatteryConsumerInfoHelper {
String packageWithHighestDrain = uidBatteryConsumer.getPackageWithHighestDrain();
if (uid == Process.ROOT_UID) {
info.label = "<root>";
+ } else if (uid < Process.FIRST_APPLICATION_UID) {
+ info.label = makeSystemUidLabel(uid);
} else {
String[] packages = packageManager.getPackagesForUid(uid);
String primaryPackageName = null;
@@ -134,6 +138,23 @@ class BatteryConsumerInfoHelper {
return info;
}
+ private static CharSequence makeSystemUidLabel(int uid) {
+ for (Field field : Process.class.getDeclaredFields()) {
+ final int modifiers = field.getModifiers();
+ if (Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers)
+ && field.getType().equals(int.class) && field.getName().endsWith("_UID")) {
+ try {
+ if (uid == field.getInt(null)) {
+ String label = field.getName();
+ return label.substring(0, label.lastIndexOf("_UID"));
+ }
+ } catch (IllegalAccessException ignored) {
+ }
+ }
+ }
+ return null;
+ }
+
private static BatteryConsumerInfo makeAggregateBatteryConsumerInfo(
BatteryUsageStats batteryUsageStats) {
BatteryConsumerInfo info = new BatteryConsumerInfo();
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerPickerActivity.java b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerPickerActivity.java
index 4469168a77b4..3699690aca59 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerPickerActivity.java
+++ b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerPickerActivity.java
@@ -30,8 +30,8 @@ import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
-import androidx.activity.ComponentActivity;
import androidx.annotation.NonNull;
+import androidx.appcompat.app.AppCompatActivity;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader;
import androidx.recyclerview.widget.LinearLayoutManager;
@@ -50,7 +50,7 @@ import java.util.Locale;
* Picker, showing a sorted lists of applications and other types of entities consuming power.
* Opens BatteryStatsViewerActivity upon item selection.
*/
-public class BatteryConsumerPickerActivity extends ComponentActivity {
+public class BatteryConsumerPickerActivity extends AppCompatActivity {
private static final String PREF_SELECTED_BATTERY_CONSUMER = "batteryConsumerId";
private static final int BATTERY_STATS_REFRESH_RATE_MILLIS = 60 * 1000;
private static final String FORCE_FRESH_STATS = "force_fresh_stats";
@@ -68,6 +68,7 @@ public class BatteryConsumerPickerActivity extends ComponentActivity {
super.onCreate(icicle);
setContentView(R.layout.battery_consumer_picker_layout);
+ setSupportActionBar(findViewById(R.id.toolbar));
mSwipeRefreshLayout = findViewById(R.id.swipe_refresh);
mSwipeRefreshLayout.setColorSchemeResources(android.R.color.holo_green_light);
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryStatsViewerActivity.java b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryStatsViewerActivity.java
index e165c49ff55d..350213161b7b 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryStatsViewerActivity.java
+++ b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryStatsViewerActivity.java
@@ -17,14 +17,18 @@
package com.android.frameworks.core.batterystatsviewer;
import android.content.Context;
+import android.os.BatteryConsumer;
import android.os.BatteryStatsManager;
import android.os.BatteryUsageStats;
import android.os.BatteryUsageStatsQuery;
import android.os.Bundle;
+import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
+import android.widget.TableLayout;
+import android.widget.TableRow;
import android.widget.TextView;
import android.widget.Toast;
@@ -40,6 +44,7 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.android.settingslib.utils.AsyncLoaderCompat;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
@@ -63,7 +68,16 @@ public class BatteryStatsViewerActivity extends ComponentActivity {
private SwipeRefreshLayout mSwipeRefreshLayout;
private View mCardView;
private View mEmptyView;
- private List<BatteryUsageStats> mBatteryUsageStats;
+ private BatteryUsageStats mBatteryUsageStats;
+
+ private static SparseArray<String> sProcStateNames = new SparseArray<>();
+ static {
+ sProcStateNames.put(BatteryConsumer.PROCESS_STATE_UNSPECIFIED, "-");
+ sProcStateNames.put(BatteryConsumer.PROCESS_STATE_FOREGROUND, "FG");
+ sProcStateNames.put(BatteryConsumer.PROCESS_STATE_BACKGROUND, "BG");
+ sProcStateNames.put(BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE, "FGS");
+ sProcStateNames.put(BatteryConsumer.PROCESS_STATE_CACHED, "Cached");
+ }
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
@@ -122,7 +136,7 @@ public class BatteryStatsViewerActivity extends ComponentActivity {
}
private static class BatteryUsageStatsLoader extends
- AsyncLoaderCompat<List<BatteryUsageStats>> {
+ AsyncLoaderCompat<BatteryUsageStats> {
private final BatteryStatsManager mBatteryStatsManager;
private final boolean mForceFreshStats;
@@ -133,51 +147,44 @@ public class BatteryStatsViewerActivity extends ComponentActivity {
}
@Override
- public List<BatteryUsageStats> loadInBackground() {
+ public BatteryUsageStats loadInBackground() {
final int maxStatsAgeMs = mForceFreshStats ? 0 : BATTERY_STATS_REFRESH_RATE_MILLIS;
final BatteryUsageStatsQuery queryDefault =
new BatteryUsageStatsQuery.Builder()
- .includePowerModels()
.includeProcessStateData()
+ .includeScreenStateData()
+ .includePowerStateData()
.setMaxStatsAgeMs(maxStatsAgeMs)
.build();
- final BatteryUsageStatsQuery queryPowerProfileModeledOnly =
- new BatteryUsageStatsQuery.Builder()
- .powerProfileModeledOnly()
- .includePowerModels()
- .includeProcessStateData()
- .setMaxStatsAgeMs(maxStatsAgeMs)
- .build();
- return mBatteryStatsManager.getBatteryUsageStats(
- List.of(queryDefault, queryPowerProfileModeledOnly));
+ return mBatteryStatsManager.getBatteryUsageStats(queryDefault);
}
@Override
- protected void onDiscardResult(List<BatteryUsageStats> result) {
+ protected void onDiscardResult(BatteryUsageStats result) {
}
}
private class BatteryUsageStatsLoaderCallbacks
- implements LoaderCallbacks<List<BatteryUsageStats>> {
+ implements LoaderCallbacks<BatteryUsageStats> {
@NonNull
@Override
- public Loader<List<BatteryUsageStats>> onCreateLoader(int id, Bundle args) {
+ public Loader<BatteryUsageStats> onCreateLoader(int id, Bundle args) {
return new BatteryUsageStatsLoader(BatteryStatsViewerActivity.this,
args.getBoolean(FORCE_FRESH_STATS));
}
@Override
- public void onLoadFinished(@NonNull Loader<List<BatteryUsageStats>> loader,
- List<BatteryUsageStats> batteryUsageStats) {
+ public void onLoadFinished(@NonNull Loader<BatteryUsageStats> loader,
+ BatteryUsageStats batteryUsageStats) {
onBatteryUsageStatsLoaded(batteryUsageStats);
}
@Override
- public void onLoaderReset(@NonNull Loader<List<BatteryUsageStats>> loader) {
+ public void onLoaderReset(@NonNull Loader<BatteryUsageStats> loader) {
}
}
- private void onBatteryUsageStatsLoaded(List<BatteryUsageStats> batteryUsageStats) {
+ private void onBatteryUsageStatsLoaded(BatteryUsageStats batteryUsageStats) {
mBatteryUsageStats = batteryUsageStats;
onBatteryStatsDataLoaded();
}
@@ -238,10 +245,21 @@ public class BatteryStatsViewerActivity extends ComponentActivity {
private static class BatteryStatsDataAdapter extends
RecyclerView.Adapter<BatteryStatsDataAdapter.ViewHolder> {
public static class ViewHolder extends RecyclerView.ViewHolder {
+ public static class SliceViewHolder {
+ public TableRow tableRow;
+ public int procState;
+ public int powerState;
+ public int screenState;
+ public TextView powerTextView;
+ public TextView durationTextView;
+ }
+
public ImageView iconImageView;
public TextView titleTextView;
public TextView value1TextView;
public TextView value2TextView;
+ public TableLayout table;
+ public List<SliceViewHolder> slices = new ArrayList<>();
ViewHolder(View itemView) {
super(itemView);
@@ -250,6 +268,40 @@ public class BatteryStatsViewerActivity extends ComponentActivity {
titleTextView = itemView.findViewById(R.id.title);
value1TextView = itemView.findViewById(R.id.value1);
value2TextView = itemView.findViewById(R.id.value2);
+ table = itemView.findViewById(R.id.table);
+
+ for (int i = 0; i < sProcStateNames.size(); i++) {
+ int procState = sProcStateNames.keyAt(i);
+ slices.add(createSliceViewHolder(procState,
+ BatteryConsumer.POWER_STATE_BATTERY,
+ BatteryConsumer.SCREEN_STATE_ON,
+ R.id.power_b_on, R.id.duration_b_on));
+ slices.add(createSliceViewHolder(procState,
+ BatteryConsumer.POWER_STATE_BATTERY,
+ BatteryConsumer.SCREEN_STATE_OTHER,
+ R.id.power_b_off, R.id.duration_b_off));
+ slices.add(createSliceViewHolder(procState,
+ BatteryConsumer.POWER_STATE_OTHER,
+ BatteryConsumer.SCREEN_STATE_ON,
+ R.id.power_c_on, R.id.duration_c_on));
+ slices.add(createSliceViewHolder(procState,
+ BatteryConsumer.POWER_STATE_OTHER,
+ BatteryConsumer.SCREEN_STATE_OTHER,
+ R.id.power_c_off, R.id.duration_c_off));
+ }
+ }
+
+ private SliceViewHolder createSliceViewHolder(int procState, int powerState,
+ int screenState, int powerTextViewResId, int durationTextViewResId) {
+ TableRow powerRow = table.findViewWithTag("procstate" + procState);
+ SliceViewHolder svh = new SliceViewHolder();
+ svh.tableRow = powerRow;
+ svh.procState = procState;
+ svh.powerState = powerState;
+ svh.screenState = screenState;
+ svh.powerTextView = powerRow.findViewById(powerTextViewResId);
+ svh.durationTextView = powerRow.findViewById(durationTextViewResId);
+ return svh;
}
}
@@ -269,62 +321,32 @@ public class BatteryStatsViewerActivity extends ComponentActivity {
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int position) {
LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
- View itemView = layoutInflater.inflate(R.layout.battery_consumer_entry_layout, parent,
- false);
+ ViewGroup itemView = (ViewGroup) layoutInflater.inflate(
+ R.layout.battery_consumer_entry_layout, parent, false);
+ TableLayout table = itemView.findViewById(R.id.table);
+ int offset = 1; // Skip header
+ for (int i = 0; i < sProcStateNames.size(); i++) {
+ View powerRow = layoutInflater.inflate(R.layout.battery_consumer_slices_layout,
+ itemView, false);
+ ((TextView) powerRow.findViewById(R.id.procState))
+ .setText(sProcStateNames.valueAt(i));
+ powerRow.setTag("procstate" + sProcStateNames.keyAt(i));
+ table.addView(powerRow, offset++);
+ }
+
return new ViewHolder(itemView);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder viewHolder, int position) {
BatteryConsumerData.Entry entry = mEntries.get(position);
-
switch (entry.entryType) {
- case UID_TOTAL_POWER:
- setTitleIconAndBackground(viewHolder, entry.title,
- R.drawable.gm_sum_24, 0);
- setPowerText(viewHolder.value1TextView, entry.value1);
- setProportionText(viewHolder.value2TextView, entry);
- break;
- case UID_POWER_PROFILE:
- setTitleIconAndBackground(viewHolder, entry.title,
- R.drawable.gm_calculate_24,
- R.color.battery_consumer_bg_power_profile);
- setPowerText(viewHolder.value1TextView, entry.value1);
- setProportionText(viewHolder.value2TextView, entry);
- break;
- case UID_POWER_PROFILE_PROCESS_STATE:
- setTitleIconAndBackground(viewHolder, " " + entry.title,
- R.drawable.gm_calculate_24,
- R.color.battery_consumer_bg_power_profile);
- setPowerText(viewHolder.value1TextView, entry.value1);
- viewHolder.value2TextView.setVisibility(View.INVISIBLE);
- break;
- case UID_POWER_ENERGY_CONSUMPTION:
- setTitleIconAndBackground(viewHolder, entry.title,
- R.drawable.gm_energy_24,
- R.color.battery_consumer_bg_energy_consumption);
- setPowerText(viewHolder.value1TextView, entry.value1);
- setProportionText(viewHolder.value2TextView, entry);
- break;
- case UID_POWER_ENERGY_PROCESS_STATE:
- setTitleIconAndBackground(viewHolder, " " + entry.title,
- R.drawable.gm_energy_24,
- R.color.battery_consumer_bg_energy_consumption);
- setPowerText(viewHolder.value1TextView, entry.value1);
- viewHolder.value2TextView.setVisibility(View.INVISIBLE);
- break;
- case UID_POWER_CUSTOM:
+ case UID:
setTitleIconAndBackground(viewHolder, entry.title,
- R.drawable.gm_energy_24,
- R.color.battery_consumer_bg_energy_consumption);
+ R.drawable.gm_energy_24, 0);
setPowerText(viewHolder.value1TextView, entry.value1);
setProportionText(viewHolder.value2TextView, entry);
- break;
- case UID_DURATION:
- setTitleIconAndBackground(viewHolder, entry.title,
- R.drawable.gm_timer_24, 0);
- setDurationText(viewHolder.value1TextView, (long) entry.value1);
- setProportionText(viewHolder.value2TextView, entry);
+ bindSlices(viewHolder, entry);
break;
case DEVICE_TOTAL_POWER:
setTitleIconAndBackground(viewHolder, entry.title,
@@ -332,27 +354,13 @@ public class BatteryStatsViewerActivity extends ComponentActivity {
setPowerText(viewHolder.value1TextView, entry.value1);
setPowerText(viewHolder.value2TextView, entry.value2);
break;
- case DEVICE_POWER_MODELED:
+ case DEVICE_POWER:
setTitleIconAndBackground(viewHolder, entry.title,
R.drawable.gm_calculate_24,
R.color.battery_consumer_bg_power_profile);
setPowerText(viewHolder.value1TextView, entry.value1);
setPowerText(viewHolder.value2TextView, entry.value2);
break;
- case DEVICE_POWER_ENERGY_CONSUMPTION:
- setTitleIconAndBackground(viewHolder, entry.title,
- R.drawable.gm_energy_24,
- R.color.battery_consumer_bg_energy_consumption);
- setPowerText(viewHolder.value1TextView, entry.value1);
- setPowerText(viewHolder.value2TextView, entry.value2);
- break;
- case DEVICE_POWER_CUSTOM:
- setTitleIconAndBackground(viewHolder, entry.title,
- R.drawable.gm_energy_24,
- R.color.battery_consumer_bg_energy_consumption);
- setPowerText(viewHolder.value1TextView, entry.value1);
- setPowerText(viewHolder.value2TextView, entry.value2);
- break;
case DEVICE_DURATION:
setTitleIconAndBackground(viewHolder, entry.title,
R.drawable.gm_timer_24, 0);
@@ -362,6 +370,65 @@ public class BatteryStatsViewerActivity extends ComponentActivity {
}
}
+ private void bindSlices(ViewHolder viewHolder, BatteryConsumerData.Entry entry) {
+ if (entry.slices == null || entry.slices.isEmpty()) {
+ viewHolder.table.setVisibility(View.GONE);
+ return;
+ }
+ viewHolder.table.setVisibility(View.VISIBLE);
+
+ boolean[] procStateRowPopulated =
+ new boolean[BatteryConsumer.PROCESS_STATE_COUNT];
+ for (BatteryConsumerData.Slice s : entry.slices) {
+ if (s.powerMah != 0 || s.durationMs != 0) {
+ procStateRowPopulated[s.processState] = true;
+ }
+ }
+
+ for (ViewHolder.SliceViewHolder sliceViewHolder : viewHolder.slices) {
+ BatteryConsumerData.Slice slice = null;
+ for (BatteryConsumerData.Slice s : entry.slices) {
+ if (s.powerState == sliceViewHolder.powerState
+ && s.screenState == sliceViewHolder.screenState
+ && s.processState == sliceViewHolder.procState) {
+ slice = s;
+ break;
+ }
+ }
+ if (!procStateRowPopulated[sliceViewHolder.procState]) {
+ sliceViewHolder.tableRow.setVisibility(View.GONE);
+ } else {
+ sliceViewHolder.tableRow.setVisibility(View.VISIBLE);
+
+ if (slice != null && (slice.powerMah != 0 || slice.durationMs != 0)) {
+ sliceViewHolder.powerTextView.setText(
+ String.format(Locale.getDefault(), "%.1f", slice.powerMah));
+ } else {
+ sliceViewHolder.powerTextView.setText(null);
+ }
+
+ if (slice != null && slice.durationMs != 0) {
+ sliceViewHolder.durationTextView.setVisibility(View.VISIBLE);
+ String timeString;
+ if (slice.durationMs < MILLIS_IN_MINUTE) {
+ timeString = String.format(Locale.getDefault(), "%ds",
+ slice.durationMs / 1000);
+ } else if (slice.durationMs < 60 * MILLIS_IN_MINUTE) {
+ timeString = String.format(Locale.getDefault(), "%dm %ds",
+ slice.durationMs / MILLIS_IN_MINUTE,
+ (slice.durationMs % MILLIS_IN_MINUTE) / 1000);
+ } else {
+ timeString = String.format(Locale.getDefault(), "%dm",
+ slice.durationMs / MILLIS_IN_MINUTE);
+ }
+ sliceViewHolder.durationTextView.setText(timeString);
+ } else {
+ sliceViewHolder.durationTextView.setVisibility(View.GONE);
+ }
+ }
+ }
+ }
+
private void setTitleIconAndBackground(ViewHolder viewHolder, String title, int icon,
int background) {
viewHolder.titleTextView.setText(title);
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/TrampolineActivity.java b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/TrampolineActivity.java
index b01648838695..412169e7a905 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/TrampolineActivity.java
+++ b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/TrampolineActivity.java
@@ -42,5 +42,6 @@ public class TrampolineActivity extends Activity {
private void launchMainActivity() {
startActivity(new Intent(this, BatteryConsumerPickerActivity.class));
+ finish();
}
}
diff --git a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
index 2fc72e1d3994..177c7f0b2f27 100644
--- a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
+++ b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
@@ -26,6 +26,7 @@ import static android.app.PropertyInvalidatedCache.NonceStore.INVALID_NONCE_INDE
import static com.android.internal.os.Flags.FLAG_APPLICATION_SHARED_MEMORY_ENABLED;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertSame;
@@ -34,6 +35,8 @@ import static org.junit.Assert.fail;
import android.annotation.SuppressLint;
import android.app.PropertyInvalidatedCache.Args;
+import android.app.PropertyInvalidatedCache.NonceWatcher;
+import android.app.PropertyInvalidatedCache.NonceStore;
import android.os.Binder;
import com.android.internal.os.ApplicationSharedMemory;
@@ -45,11 +48,15 @@ import android.platform.test.ravenwood.RavenwoodRule;
import androidx.test.filters.SmallTest;
+import com.android.internal.os.ApplicationSharedMemory;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
+import java.util.concurrent.TimeUnit;
+
/**
* Test for verifying the behavior of {@link PropertyInvalidatedCache}. This test does
* not use any actual binder calls - it is entirely self-contained. This test also relies
@@ -490,6 +497,62 @@ public class PropertyInvalidatedCacheTests {
}
}
+ // Verify that NonceWatcher change reporting works properly
+ @Test
+ public void testNonceWatcherChanged() {
+ // Create a cache that will write a system nonce.
+ TestCache sysCache = new TestCache(MODULE_SYSTEM, "watcher1");
+ sysCache.testPropertyName();
+
+ try (NonceWatcher watcher1 = sysCache.getNonceWatcher()) {
+
+ // The property has never been invalidated so it is still unset.
+ assertFalse(watcher1.isChanged());
+
+ // Invalidate the cache. The first call to isChanged will return true but the second
+ // call will return false;
+ sysCache.invalidateCache();
+ assertTrue(watcher1.isChanged());
+ assertFalse(watcher1.isChanged());
+
+ // Invalidate the cache. The first call to isChanged will return true but the second
+ // call will return false;
+ sysCache.invalidateCache();
+ sysCache.invalidateCache();
+ assertTrue(watcher1.isChanged());
+ assertFalse(watcher1.isChanged());
+
+ NonceWatcher watcher2 = sysCache.getNonceWatcher();
+ // This watcher return isChanged() immediately because the nonce is not UNSET.
+ assertTrue(watcher2.isChanged());
+ }
+ }
+
+ // Verify that NonceWatcher wait-for-change works properly
+ @Test
+ public void testNonceWatcherWait() throws Exception {
+ // Create a cache that will write a system nonce.
+ TestCache sysCache = new TestCache(MODULE_TEST, "watcher1");
+
+ // Use the watcher outside a try-with-resources block.
+ NonceWatcher watcher1 = sysCache.getNonceWatcher();
+
+ // Invalidate the cache and then "wait".
+ sysCache.invalidateCache();
+ assertEquals(watcher1.waitForChange(), 1);
+
+ // Invalidate the cache three times and then "wait".
+ sysCache.invalidateCache();
+ sysCache.invalidateCache();
+ sysCache.invalidateCache();
+ assertEquals(watcher1.waitForChange(), 3);
+
+ // Wait for a change. It won't happen, but the code will time out after 10ms.
+ assertEquals(watcher1.waitForChange(10, TimeUnit.MILLISECONDS), 0);
+
+ watcher1.close();
+ }
+
// Verify the behavior of shared memory nonce storage. This does not directly test the cache
// storing nonces in shared memory.
@RequiresFlagsEnabled(FLAG_APPLICATION_SHARED_MEMORY_ENABLED)
@@ -502,10 +565,8 @@ public class PropertyInvalidatedCacheTests {
// Create a server-side store and a client-side store. The server's store is mutable and
// the client's store is not mutable.
- PropertyInvalidatedCache.NonceStore server =
- new PropertyInvalidatedCache.NonceStore(shmem.getSystemNonceBlock(), true);
- PropertyInvalidatedCache.NonceStore client =
- new PropertyInvalidatedCache.NonceStore(shmem.getSystemNonceBlock(), false);
+ NonceStore server = new NonceStore(shmem.getSystemNonceBlock(), true);
+ NonceStore client = new NonceStore(shmem.getSystemNonceBlock(), false);
final String name1 = "name1";
assertEquals(server.getHandleForName(name1), INVALID_NONCE_INDEX);
diff --git a/core/tests/coretests/src/android/os/OWNERS b/core/tests/coretests/src/android/os/OWNERS
index 4620cb8d8148..c45080fb5e26 100644
--- a/core/tests/coretests/src/android/os/OWNERS
+++ b/core/tests/coretests/src/android/os/OWNERS
@@ -15,3 +15,6 @@ per-file IpcDataCache* = file:/PERFORMANCE_OWNERS
# RemoteCallbackList
per-file RemoteCallbackListTest.java = shayba@google.com
+
+# MessageQueue
+per-file MessageQueueTest.java = mfasheh@google.com, shayba@google.com
diff --git a/core/tests/coretests/src/android/view/ScrollCaptureSearchResultsTest.java b/core/tests/coretests/src/android/view/ScrollCaptureSearchResultsTest.java
index 726ee85dddd5..915ace058121 100644
--- a/core/tests/coretests/src/android/view/ScrollCaptureSearchResultsTest.java
+++ b/core/tests/coretests/src/android/view/ScrollCaptureSearchResultsTest.java
@@ -16,9 +16,9 @@
package android.view;
-import static androidx.test.InstrumentationRegistry.getTargetContext;
+import static android.view.flags.Flags.FLAG_SCROLL_CAPTURE_TARGET_Z_ORDER_FIX;
-import static com.google.common.truth.Truth.assertThat;
+import static androidx.test.InstrumentationRegistry.getTargetContext;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
@@ -32,13 +32,16 @@ import android.graphics.Point;
import android.graphics.Rect;
import android.os.CancellationSignal;
import android.os.SystemClock;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import androidx.annotation.NonNull;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -56,6 +59,8 @@ import java.util.function.Consumer;
@RunWith(AndroidJUnit4.class)
public class ScrollCaptureSearchResultsTest {
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private static final Rect EMPTY_RECT = new Rect();
private static final String TAG = "Test";
@@ -98,6 +103,45 @@ public class ScrollCaptureSearchResultsTest {
assertNull("Expected null due to no valid targets", results.getTopResult());
}
+ /**
+ * A scrolling target should be excluded even when larger if it will be drawn over by another
+ * scrolling target.
+ */
+ @EnableFlags(FLAG_SCROLL_CAPTURE_TARGET_Z_ORDER_FIX)
+ @Test
+ public void testCoveredTargetsAreExcluded() {
+ ScrollCaptureSearchResults results = new ScrollCaptureSearchResults(mDirectExec);
+
+ FakeScrollCaptureCallback callback1 = new FakeScrollCaptureCallback(mDirectExec);
+ callback1.setScrollBounds(new Rect(0, 0, 200, 200)); // 200 tall
+ View view1 = new FakeView(getTargetContext(), 0, 0, 200, 200, 1);
+ ScrollCaptureTarget target1 = createTargetWithView(view1, callback1,
+ new Rect(0, 0, 200, 200), new Point(0, 0), View.SCROLL_CAPTURE_HINT_AUTO);
+
+ FakeScrollCaptureCallback callback2 = new FakeScrollCaptureCallback(mDirectExec);
+ callback2.setScrollBounds(new Rect(0, 0, 200, 180)); // 180 tall
+ View view2 = new FakeView(getTargetContext(), 0, 20, 200, 200, 2);
+ ScrollCaptureTarget target2 = createTargetWithView(view2, callback2,
+ new Rect(0, 0, 200, 180), new Point(0, 20), View.SCROLL_CAPTURE_HINT_AUTO);
+
+ // Top z-order but smaller, and non-intersecting. (positioned further Y than the first two)
+ FakeScrollCaptureCallback callback3 = new FakeScrollCaptureCallback(mDirectExec);
+ callback3.setScrollBounds(new Rect(0, 0, 50, 50));
+ View view3 = new FakeView(getTargetContext(), 75, 250, 125, 300, 3);
+ ScrollCaptureTarget target3 = createTargetWithView(view3, callback3,
+ new Rect(0, 0, 50, 50), new Point(75, 250), View.SCROLL_CAPTURE_HINT_AUTO);
+
+ results.addTarget(target1);
+ results.addTarget(target2);
+ results.addTarget(target3);
+
+ assertTrue(results.isComplete());
+ ScrollCaptureTarget result = results.getTopResult();
+ assertSame("Expected the second target because of higher z-Index", target2, result);
+ assertEquals("result has wrong scroll bounds",
+ new Rect(0, 0, 200, 180), result.getScrollBounds());
+ }
+
@Test
public void testSingleTarget() {
ScrollCaptureSearchResults results = new ScrollCaptureSearchResults(mDirectExec);
@@ -152,29 +196,29 @@ public class ScrollCaptureSearchResultsTest {
// 2 - 10x10 + HINT_INCLUDE
FakeScrollCaptureCallback callback2 = new FakeScrollCaptureCallback(mDirectExec);
- callback2.setScrollBounds(new Rect(0, 0, 10, 10));
- ViewGroup targetView2 = new FakeView(getTargetContext(), 0, 0, 60, 60, 2);
+ callback2.setScrollBounds(new Rect(25, 25, 35, 35)); // 10x10
+ ViewGroup targetView2 = new FakeView(getTargetContext(), 0, 60, 60, 120, 2);
ScrollCaptureTarget target2 = createTargetWithView(targetView2, callback2,
new Rect(0, 0, 60, 60), new Point(0, 0), View.SCROLL_CAPTURE_HINT_INCLUDE);
// 3 - 20x20 + AUTO
FakeScrollCaptureCallback callback3 = new FakeScrollCaptureCallback(mDirectExec);
callback3.setScrollBounds(new Rect(0, 0, 20, 20));
- ViewGroup targetView3 = new FakeView(getTargetContext(), 0, 0, 60, 60, 3);
+ ViewGroup targetView3 = new FakeView(getTargetContext(), 0, 120, 60, 180, 3);
ScrollCaptureTarget target3 = createTargetWithView(targetView3, callback3,
new Rect(0, 0, 60, 60), new Point(0, 0), View.SCROLL_CAPTURE_HINT_AUTO);
// 4 - 30x30 + AUTO
FakeScrollCaptureCallback callback4 = new FakeScrollCaptureCallback(mDirectExec);
callback4.setScrollBounds(new Rect(0, 0, 10, 10));
- ViewGroup targetView4 = new FakeView(getTargetContext(), 0, 0, 60, 60, 4);
+ ViewGroup targetView4 = new FakeView(getTargetContext(), 0, 180, 60, 240, 4);
ScrollCaptureTarget target4 = createTargetWithView(targetView4, callback4,
new Rect(0, 0, 60, 60), new Point(0, 0), View.SCROLL_CAPTURE_HINT_AUTO);
// 5 - 10x10 + child of #4
FakeScrollCaptureCallback callback5 = new FakeScrollCaptureCallback(mDirectExec);
callback5.setScrollBounds(new Rect(0, 0, 10, 10));
- ViewGroup targetView5 = new FakeView(getTargetContext(), 0, 0, 60, 60, 5);
+ ViewGroup targetView5 = new FakeView(getTargetContext(), 0, 0, 60, 30, 5);
ScrollCaptureTarget target5 = createTargetWithView(targetView5, callback5,
new Rect(0, 0, 60, 60), new Point(0, 0), View.SCROLL_CAPTURE_HINT_AUTO);
targetView4.addView(targetView5);
@@ -182,7 +226,7 @@ public class ScrollCaptureSearchResultsTest {
// 6 - 20x20 + child of #4
FakeScrollCaptureCallback callback6 = new FakeScrollCaptureCallback(mDirectExec);
callback6.setScrollBounds(new Rect(0, 0, 20, 20));
- ViewGroup targetView6 = new FakeView(getTargetContext(), 0, 0, 60, 60, 6);
+ ViewGroup targetView6 = new FakeView(getTargetContext(), 0, 30, 30, 60, 6);
ScrollCaptureTarget target6 = createTargetWithView(targetView6, callback6,
new Rect(0, 0, 60, 60), new Point(0, 0), View.SCROLL_CAPTURE_HINT_AUTO);
targetView4.addView(targetView6);
@@ -194,20 +238,10 @@ public class ScrollCaptureSearchResultsTest {
results.addTarget(target4);
results.addTarget(target5);
results.addTarget(target6);
- assertTrue(results.isComplete());
+ assertTrue("results.isComplete()", results.isComplete());
// Verify "top" result
- assertEquals(target2, results.getTopResult());
-
- // Verify priority ("best" first)
- assertThat(results.getTargets())
- .containsExactly(
- target2,
- target6,
- target5,
- target4,
- target3,
- target1);
+ assertEquals("top result", target2, results.getTopResult());
}
/**
@@ -291,27 +325,22 @@ public class ScrollCaptureSearchResultsTest {
new Rect(1, 2, 3, 4), result.getScrollBounds());
}
- private void setupTargetView(View view, Rect localVisibleRect, int scrollCaptureHint) {
- view.setScrollCaptureHint(scrollCaptureHint);
- view.onVisibilityAggregated(true);
- // Treat any offset as padding, outset localVisibleRect on all sides and use this as
- // child bounds
- Rect bounds = new Rect(localVisibleRect);
- bounds.inset(-bounds.left, -bounds.top, bounds.left, bounds.top);
- view.layout(bounds.left, bounds.top, bounds.right, bounds.bottom);
- view.onVisibilityAggregated(true);
- }
-
private ScrollCaptureTarget createTarget(ScrollCaptureCallback callback, Rect localVisibleRect,
Point positionInWindow, int scrollCaptureHint) {
- View mockView = new View(getTargetContext());
+ Rect bounds = new Rect(localVisibleRect);
+ // Use localVisibleRect as position, treat left/top offset as padding
+ bounds.left = 0;
+ bounds.top = 0;
+ View mockView = new FakeView(getTargetContext(), bounds.left, bounds.top, bounds.right,
+ bounds.bottom, View.NO_ID);
return createTargetWithView(mockView, callback, localVisibleRect, positionInWindow,
scrollCaptureHint);
}
private ScrollCaptureTarget createTargetWithView(View view, ScrollCaptureCallback callback,
Rect localVisibleRect, Point positionInWindow, int scrollCaptureHint) {
- setupTargetView(view, localVisibleRect, scrollCaptureHint);
+ view.setScrollCaptureHint(scrollCaptureHint);
+ view.onVisibilityAggregated(true);
return new ScrollCaptureTarget(view, localVisibleRect, positionInWindow, callback);
}
@@ -326,6 +355,32 @@ public class ScrollCaptureSearchResultsTest {
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
}
+
+ /** Ignores window attachment state. The standard impl always returns [0,0] if the view is
+ * not attached. This override allows testing without dealing with AttachInfo.
+ */
+ @Override
+ public void getLocationInWindow(int[] outLocation) {
+ outLocation[0] = mLeft;
+ outLocation[1] = mTop;
+ ViewParent viewParent = getParent();
+ while (viewParent instanceof View) {
+ final View view = (View) viewParent;
+
+ outLocation[0] -= view.mScrollX;
+ outLocation[1] -= view.mScrollY;
+
+ // Explicitly do not handle matrix/transforms, not needed for testing
+ if (!view.hasIdentityMatrix()) {
+ throw new IllegalStateException("This mock does not handle transforms!");
+ }
+
+ outLocation[0] += view.mLeft;
+ outLocation[1] += view.mTop;
+
+ viewParent = view.mParent;
+ }
+ }
}
static class FakeScrollCaptureCallback implements ScrollCaptureCallback {
diff --git a/core/tests/coretests/src/android/view/ViewGroupScrollCaptureTest.java b/core/tests/coretests/src/android/view/ViewGroupScrollCaptureTest.java
index 25608c328f95..adf7a720071a 100644
--- a/core/tests/coretests/src/android/view/ViewGroupScrollCaptureTest.java
+++ b/core/tests/coretests/src/android/view/ViewGroupScrollCaptureTest.java
@@ -16,6 +16,8 @@
package android.view;
+import static android.view.flags.Flags.FLAG_SCROLL_CAPTURE_TARGET_Z_ORDER_FIX;
+
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static org.junit.Assert.assertEquals;
@@ -30,16 +32,20 @@ import android.content.Context;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.CancellationSignal;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import androidx.annotation.NonNull;
import androidx.test.filters.MediumTest;
import androidx.test.filters.SmallTest;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
+import java.util.List;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
@@ -51,6 +57,9 @@ import java.util.function.Consumer;
@RunWith(MockitoJUnitRunner.class)
public class ViewGroupScrollCaptureTest {
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
private static final Executor DIRECT_EXECUTOR = Runnable::run;
/** Make sure the hint flags are saved and loaded correctly. */
@@ -239,6 +248,56 @@ public class ViewGroupScrollCaptureTest {
assertNull(results.getTopResult());
}
+ @EnableFlags(FLAG_SCROLL_CAPTURE_TARGET_Z_ORDER_FIX)
+ @MediumTest
+ @Test
+ public void testDispatchScrollCaptureSearch_traversesInDrawingOrder() throws Exception {
+ final Context context = getInstrumentation().getContext();
+ // Uses childDrawingOrder to reverse drawing order of children.
+ final MockViewGroup viewGroup = new MockViewGroup(context, 0, 0, 200, 200);
+
+ // w=200, h=180, z=10, drawn on top
+ final MockView view1 = new MockView(context, 0, 20, 200, 200);
+ TestScrollCaptureCallback callback1 = new TestScrollCaptureCallback();
+ view1.setScrollCaptureCallback(callback1);
+ view1.setZ(10f);
+
+ // w=200, h=200, z=0, drawn first, under view1
+ final MockView view2 = new MockView(context, 0, 0, 200, 200);
+ TestScrollCaptureCallback callback2 = new TestScrollCaptureCallback();
+ view2.setScrollCaptureCallback(callback2);
+
+ viewGroup.addView(view1); // test order is dependent on draw order by adding z=10 first
+ viewGroup.addView(view2);
+
+ Rect localVisibleRect = new Rect(0, 0, 200, 200);
+ Point windowOffset = new Point(0, 0);
+
+ // Where targets are added
+ final ScrollCaptureSearchResults results = new ScrollCaptureSearchResults(DIRECT_EXECUTOR);
+
+ viewGroup.dispatchScrollCaptureSearch(localVisibleRect, windowOffset, results::addTarget);
+ callback1.completeSearchRequest(new Rect(0, 0, 200, 180));
+ callback2.completeSearchRequest(new Rect(0, 0, 200, 200));
+ assertTrue(results.isComplete());
+
+ List<ScrollCaptureTarget> targets = results.getTargets();
+ List<View> targetViews =
+ targets.stream().map(ScrollCaptureTarget::getContainingView).toList();
+ assertEquals(List.of(view2, view1), targetViews);
+ }
+
+ static final class ReverseDrawingViewGroup extends MockViewGroup {
+ ReverseDrawingViewGroup(Context context, int left, int top, int right, int bottom) {
+ super(context, left, top, right, bottom, View.SCROLL_CAPTURE_HINT_AUTO);
+ }
+
+ @Override
+ protected int getChildDrawingOrder(int childCount, int drawingPosition) {
+ return childCount == 0 ? 0 : childCount - (drawingPosition + 1);
+ }
+ }
+
/**
* Test scroll capture search dispatch to child views.
* <p>
@@ -511,7 +570,7 @@ public class ViewGroupScrollCaptureTest {
}
};
- public static final class MockViewGroup extends ViewGroup {
+ public static class MockViewGroup extends ViewGroup {
private ScrollCaptureCallback mInternalCallback;
private Rect mOnScrollCaptureSearchLastLocalVisibleRect;
private Point mOnScrollCaptureSearchLastWindowOffset;
diff --git a/core/tests/coretests/src/android/widget/ProgressBarTest.java b/core/tests/coretests/src/android/widget/ProgressBarTest.java
index fa6dd31923ac..0cbfaa97faef 100644
--- a/core/tests/coretests/src/android/widget/ProgressBarTest.java
+++ b/core/tests/coretests/src/android/widget/ProgressBarTest.java
@@ -23,8 +23,12 @@ import static org.junit.Assert.assertTrue;
import android.app.Instrumentation;
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.view.View;
import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.Flags;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -32,6 +36,7 @@ import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -40,6 +45,10 @@ import org.junit.runner.RunWith;
@Presubmit
public class ProgressBarTest {
private final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
+
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
private ProgressBar mBar;
private AccessibilityNodeInfo mInfo;
@@ -181,4 +190,16 @@ public class ProgressBarTest {
mBar.onInitializeAccessibilityNodeInfo(mInfo);
assertEquals("custom state", mInfo.getStateDescription().toString());
}
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_INDETERMINATE_RANGE_INFO)
+ public void testRangeInfo_indeterminateProgressBar_usesTypeIndeterminate() {
+ mBar.setIndeterminate(true);
+ assertTrue(mBar.isIndeterminate());
+
+ mBar.onInitializeAccessibilityNodeInfo(mInfo);
+
+ assertEquals(mInfo.getRangeInfo().getType(),
+ AccessibilityNodeInfo.RangeInfo.RANGE_TYPE_INDETERMINATE);
+ }
}
diff --git a/core/tests/coretests/src/android/window/WindowContextTest.java b/core/tests/coretests/src/android/window/WindowContextTest.java
index f1fbd559b8f8..21930d17ed68 100644
--- a/core/tests/coretests/src/android/window/WindowContextTest.java
+++ b/core/tests/coretests/src/android/window/WindowContextTest.java
@@ -29,6 +29,11 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
import android.app.Activity;
import android.app.EmptyActivity;
@@ -60,6 +65,8 @@ import androidx.test.rule.ActivityTestRule;
import com.android.frameworks.coretests.R;
+import org.junit.After;
+import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -88,9 +95,21 @@ public class WindowContextTest {
private final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
private final WindowContext mWindowContext = createWindowContext();
private final IWindowManager mWms = WindowManagerGlobal.getWindowManagerService();
+ private WindowTokenClientController mOriginalWindowTokenClientController;
private static final int TIMEOUT_IN_SECONDS = 4;
+ @Before
+ public void setUp() {
+ // Keeping the original to set it back after each test, in case they applied any override.
+ mOriginalWindowTokenClientController = WindowTokenClientController.getInstance();
+ }
+
+ @After
+ public void tearDown() {
+ WindowTokenClientController.overrideForTesting(mOriginalWindowTokenClientController);
+ }
+
@Test
public void testCreateWindowContextWindowManagerAttachClientToken() {
final WindowManager windowContextWm = WindowManagerImpl
@@ -320,6 +339,20 @@ public class WindowContextTest {
}
}
+ @Test
+ public void updateDisplay_wasAttached_detachThenAttachedPropagatedToTokenController() {
+ final WindowTokenClientController mockWindowTokenClientController =
+ mock(WindowTokenClientController.class);
+ WindowTokenClientController.overrideForTesting(mockWindowTokenClientController);
+
+ mWindowContext.updateDisplay(DEFAULT_DISPLAY + 1);
+
+ verify(mockWindowTokenClientController).detachIfNeeded(any());
+ verify(mockWindowTokenClientController).attachToDisplayArea(any(),
+ anyInt(), /* displayId= */ eq(DEFAULT_DISPLAY + 1),
+ any());
+ }
+
private WindowContext createWindowContext() {
return createWindowContext(TYPE_APPLICATION_OVERLAY);
}
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index af690f4449af..897fc543517e 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -677,8 +677,4 @@ applications that come with the platform
<permission name="android.permission.BATTERY_STATS"/>
<permission name="android.permission.ENTER_TRADE_IN_MODE"/>
</privapp-permissions>
-
- <privapp-permissions package="com.android.multiuser">
- <permission name="android.permission.MANAGE_USERS"/>
- </privapp-permissions>
</permissions>
diff --git a/errorprone/Android.bp b/errorprone/Android.bp
index b559a15c3a60..1428b8965473 100644
--- a/errorprone/Android.bp
+++ b/errorprone/Android.bp
@@ -31,6 +31,14 @@ java_library_host {
"//external/auto:auto_service_annotations",
],
+ javacflags: [
+ // These exports are needed because this errorprone plugin access some private classes
+ // of the java compiler.
+ "--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED",
+ "--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED",
+ "--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED",
+ ],
+
plugins: [
"//external/auto:auto_service_plugin",
],
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt
new file mode 100644
index 000000000000..f535fbd653c5
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt
@@ -0,0 +1,320 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.bubbles
+
+import android.content.Context
+import android.content.pm.LauncherApps
+import android.graphics.Insets
+import android.graphics.Rect
+import android.os.Handler
+import android.os.UserManager
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
+import android.view.IWindowManager
+import android.view.WindowManager
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.internal.protolog.ProtoLog
+import com.android.internal.statusbar.IStatusBarService
+import com.android.wm.shell.Flags
+import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.WindowManagerShellWrapper
+import com.android.wm.shell.bubbles.Bubbles.SysuiProxy
+import com.android.wm.shell.bubbles.properties.ProdBubbleProperties
+import com.android.wm.shell.bubbles.storage.BubblePersistentRepository
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.DisplayInsetsController
+import com.android.wm.shell.common.FloatingContentCoordinator
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.common.SyncTransactionQueue
+import com.android.wm.shell.common.TaskStackListenerImpl
+import com.android.wm.shell.draganddrop.DragAndDropController
+import com.android.wm.shell.shared.TransactionPool
+import com.android.wm.shell.shared.bubbles.BubbleBarLocation
+import com.android.wm.shell.shared.bubbles.BubbleBarUpdate
+import com.android.wm.shell.sysui.ShellCommandHandler
+import com.android.wm.shell.sysui.ShellController
+import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.taskview.TaskViewTransitions
+import com.android.wm.shell.transition.Transitions
+import com.google.common.truth.Truth.assertThat
+import com.google.common.util.concurrent.MoreExecutors.directExecutor
+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
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+import java.util.Optional
+
+/** Tests for [BubbleController] when using bubble bar */
+@SmallTest
+@EnableFlags(Flags.FLAG_ENABLE_BUBBLE_BAR)
+@RunWith(AndroidJUnit4::class)
+class BubbleControllerBubbleBarTest {
+
+ companion object {
+ private const val SCREEN_WIDTH = 2000
+ private const val SCREEN_HEIGHT = 1000
+ }
+
+ @get:Rule val setFlagsRule = SetFlagsRule()
+
+ private val context = ApplicationProvider.getApplicationContext<Context>()
+
+ private lateinit var bubbleController: BubbleController
+ private lateinit var uiEventLoggerFake: UiEventLoggerFake
+ private lateinit var bubblePositioner: BubblePositioner
+ private lateinit var bubbleData: BubbleData
+ private lateinit var mainExecutor: TestExecutor
+ private lateinit var bgExecutor: TestExecutor
+
+ @Before
+ fun setUp() {
+ ProtoLog.REQUIRE_PROTOLOGTOOL = false
+ ProtoLog.init()
+
+ mainExecutor = TestExecutor()
+ bgExecutor = TestExecutor()
+
+ uiEventLoggerFake = UiEventLoggerFake()
+ val bubbleLogger = BubbleLogger(uiEventLoggerFake)
+
+ val deviceConfig =
+ DeviceConfig(
+ windowBounds = Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT),
+ isLargeScreen = true,
+ isSmallTablet = false,
+ isLandscape = true,
+ isRtl = false,
+ insets = Insets.of(10, 20, 30, 40),
+ )
+
+ bubblePositioner = BubblePositioner(context, deviceConfig)
+ bubblePositioner.isShowingInBubbleBar = true
+
+ bubbleData =
+ BubbleData(
+ context,
+ bubbleLogger,
+ bubblePositioner,
+ BubbleEducationController(context),
+ mainExecutor,
+ bgExecutor,
+ )
+
+ val shellInit = ShellInit(mainExecutor)
+
+ bubbleController =
+ createBubbleController(
+ shellInit,
+ bubbleData,
+ bubbleLogger,
+ bubblePositioner,
+ mainExecutor,
+ bgExecutor,
+ )
+ bubbleController.asBubbles().setSysuiProxy(Mockito.mock(SysuiProxy::class.java))
+
+ shellInit.init()
+
+ mainExecutor.flushAll()
+ bgExecutor.flushAll()
+
+ bubbleController.registerBubbleStateListener(FakeBubblesStateListener())
+ }
+
+ @After
+ fun tearDown() {
+ mainExecutor.flushAll()
+ bgExecutor.flushAll()
+ }
+
+ @Test
+ fun testEventLogging_bubbleBar_dragBarLeft() {
+ addBubble()
+
+ bubblePositioner.bubbleBarLocation = BubbleBarLocation.RIGHT
+
+ bubbleController.setBubbleBarLocation(
+ BubbleBarLocation.LEFT,
+ BubbleBarLocation.UpdateSource.DRAG_BAR,
+ )
+
+ // 2 events: add bubble + drag event
+ assertThat(uiEventLoggerFake.numLogs()).isEqualTo(2)
+ assertThat(uiEventLoggerFake.eventId(1))
+ .isEqualTo(BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_DRAG_BAR.id)
+ }
+
+ @Test
+ fun testEventLogging_bubbleBar_dragBarRight() {
+ addBubble()
+
+ bubblePositioner.bubbleBarLocation = BubbleBarLocation.LEFT
+
+ bubbleController.setBubbleBarLocation(
+ BubbleBarLocation.RIGHT,
+ BubbleBarLocation.UpdateSource.DRAG_BAR,
+ )
+
+ // 2 events: add bubble + drag event
+ assertThat(uiEventLoggerFake.numLogs()).isEqualTo(2)
+ assertThat(uiEventLoggerFake.eventId(1))
+ .isEqualTo(BubbleLogger.Event.BUBBLE_BAR_MOVED_RIGHT_DRAG_BAR.id)
+ }
+
+ @Test
+ fun testEventLogging_bubbleBar_dragBubbleLeft() {
+ addBubble()
+
+ bubblePositioner.bubbleBarLocation = BubbleBarLocation.RIGHT
+
+ bubbleController.setBubbleBarLocation(
+ BubbleBarLocation.LEFT,
+ BubbleBarLocation.UpdateSource.DRAG_BUBBLE,
+ )
+
+ // 2 events: add bubble + drag event
+ assertThat(uiEventLoggerFake.numLogs()).isEqualTo(2)
+ assertThat(uiEventLoggerFake.eventId(1))
+ .isEqualTo(BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_DRAG_BUBBLE.id)
+ }
+
+ @Test
+ fun testEventLogging_bubbleBar_dragBubbleRight() {
+ addBubble()
+
+ bubblePositioner.bubbleBarLocation = BubbleBarLocation.LEFT
+
+ bubbleController.setBubbleBarLocation(
+ BubbleBarLocation.RIGHT,
+ BubbleBarLocation.UpdateSource.DRAG_BUBBLE,
+ )
+
+ // 2 events: add bubble + drag event
+ assertThat(uiEventLoggerFake.numLogs()).isEqualTo(2)
+ assertThat(uiEventLoggerFake.eventId(1))
+ .isEqualTo(BubbleLogger.Event.BUBBLE_BAR_MOVED_RIGHT_DRAG_BUBBLE.id)
+ }
+
+ private fun addBubble(): Bubble {
+ val bubble = FakeBubbleFactory.createChatBubble(context)
+ bubble.setInflateSynchronously(true)
+ bubbleData.notificationEntryUpdated(
+ bubble,
+ /* suppressFlyout= */ true,
+ /* showInShade= */ true,
+ )
+ return bubble
+ }
+
+ private fun createBubbleController(
+ shellInit: ShellInit,
+ bubbleData: BubbleData,
+ bubbleLogger: BubbleLogger,
+ bubblePositioner: BubblePositioner,
+ mainExecutor: TestExecutor,
+ bgExecutor: TestExecutor,
+ ): BubbleController {
+ val shellCommandHandler = ShellCommandHandler()
+ val shellController =
+ ShellController(
+ context,
+ shellInit,
+ shellCommandHandler,
+ mock<DisplayInsetsController>(),
+ mainExecutor,
+ )
+ val surfaceSynchronizer = { obj: Runnable -> obj.run() }
+
+ val bubbleDataRepository =
+ BubbleDataRepository(
+ mock<LauncherApps>(),
+ mainExecutor,
+ bgExecutor,
+ BubblePersistentRepository(context),
+ )
+
+ val shellTaskOrganizer = mock<ShellTaskOrganizer>()
+ whenever(shellTaskOrganizer.executor).thenReturn(directExecutor())
+
+ return BubbleController(
+ context,
+ shellInit,
+ shellCommandHandler,
+ shellController,
+ bubbleData,
+ surfaceSynchronizer,
+ FloatingContentCoordinator(),
+ bubbleDataRepository,
+ mock<IStatusBarService>(),
+ mock<WindowManager>(),
+ WindowManagerShellWrapper(mainExecutor),
+ mock<UserManager>(),
+ mock<LauncherApps>(),
+ bubbleLogger,
+ mock<TaskStackListenerImpl>(),
+ shellTaskOrganizer,
+ bubblePositioner,
+ mock<DisplayController>(),
+ /* oneHandedOptional= */ Optional.empty(),
+ mock<DragAndDropController>(),
+ mainExecutor,
+ mock<Handler>(),
+ bgExecutor,
+ mock<TaskViewTransitions>(),
+ mock<Transitions>(),
+ SyncTransactionQueue(TransactionPool(), mainExecutor),
+ mock<IWindowManager>(),
+ ProdBubbleProperties,
+ )
+ }
+
+ private class TestExecutor : ShellExecutor {
+
+ private val runnables: MutableList<Runnable> = mutableListOf()
+
+ override fun execute(runnable: Runnable) {
+ runnables.add(runnable)
+ }
+
+ override fun executeDelayed(runnable: Runnable, delayMillis: Long) {
+ execute(runnable)
+ }
+
+ override fun removeCallbacks(runnable: Runnable?) {}
+
+ override fun hasCallback(runnable: Runnable?): Boolean = false
+
+ fun flushAll() {
+ while (runnables.isNotEmpty()) {
+ runnables.removeAt(0).run()
+ }
+ }
+ }
+
+ private class FakeBubblesStateListener : Bubbles.BubbleStateListener {
+ override fun onBubbleStateChange(update: BubbleBarUpdate?) {}
+
+ override fun animateBubbleBarLocation(location: BubbleBarLocation?) {}
+ }
+}
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleFactory.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleFactory.kt
index cb6fb62bf89b..3279d561d4f1 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleFactory.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleFactory.kt
@@ -32,10 +32,10 @@ class FakeBubbleFactory {
return BubbleViewInfo().apply { bubbleBarExpandedView = bubbleExpandedView }
}
- fun createChatBubbleWithViewInfo(
+ fun createChatBubble(
context: Context,
key: String = "key",
- viewInfo: BubbleViewInfo,
+ viewInfo: BubbleViewInfo? = null,
): Bubble {
val bubble =
Bubble(
@@ -50,7 +50,9 @@ class FakeBubbleFactory {
directExecutor(),
directExecutor(),
) {}
- bubble.setViewInfo(viewInfo)
+ if (viewInfo != null) {
+ bubble.setViewInfo(viewInfo)
+ }
return bubble
}
}
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
index 3d34cbaeaf08..7280f8aa07a6 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
@@ -165,7 +165,7 @@ class BubbleBarLayerViewTest {
}
val viewInfo = FakeBubbleFactory.createViewInfo(bubbleBarExpandedView)
- bubble = FakeBubbleFactory.createChatBubbleWithViewInfo(context, viewInfo = viewInfo)
+ bubble = FakeBubbleFactory.createChatBubble(context, viewInfo = viewInfo)
}
@After
diff --git a/libs/WindowManager/Shell/res/layout/compat_ui_layout.xml b/libs/WindowManager/Shell/res/layout/compat_ui_layout.xml
index e7ead63a8df6..62782a784db9 100644
--- a/libs/WindowManager/Shell/res/layout/compat_ui_layout.xml
+++ b/libs/WindowManager/Shell/res/layout/compat_ui_layout.xml
@@ -22,14 +22,14 @@
android:gravity="bottom|end">
<include android:id="@+id/size_compat_hint"
- android:visibility="invisible"
+ android:visibility="gone"
android:layout_width="@dimen/compat_hint_width"
android:layout_height="wrap_content"
layout="@layout/compat_mode_hint"/>
<ImageButton
android:id="@+id/size_compat_restart_button"
- android:visibility="invisible"
+ android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/compat_button_margin"
diff --git a/libs/WindowManager/Shell/res/layout/user_aspect_ratio_settings_layout.xml b/libs/WindowManager/Shell/res/layout/user_aspect_ratio_settings_layout.xml
index b5f04c3b815a..433d8546ece0 100644
--- a/libs/WindowManager/Shell/res/layout/user_aspect_ratio_settings_layout.xml
+++ b/libs/WindowManager/Shell/res/layout/user_aspect_ratio_settings_layout.xml
@@ -22,14 +22,14 @@
android:gravity="bottom|end">
<include android:id="@+id/user_aspect_ratio_settings_hint"
- android:visibility="invisible"
+ android:visibility="gone"
android:layout_width="@dimen/compat_hint_width"
android:layout_height="wrap_content"
layout="@layout/compat_mode_hint"/>
<ImageButton
android:id="@+id/user_aspect_ratio_settings_button"
- android:visibility="invisible"
+ android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/compat_button_margin"
diff --git a/libs/WindowManager/Shell/shared/res/values/strings.xml b/libs/WindowManager/Shell/shared/res/values/strings.xml
new file mode 100644
index 000000000000..d1804c04cbec
--- /dev/null
+++ b/libs/WindowManager/Shell/shared/res/values/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources>
+ <!-- Accessibility text for the icons generated by the Manage Windows submenu
+ in desktop mode and taskbar. [CHAR LIMIT=NONE] -->
+ <string name="manage_windows_icon_text">Open Window %1$d</string>
+</resources>
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/ManageWindowsViewContainer.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/multiinstance/ManageWindowsViewContainer.kt
index f14dfdbc7d30..0954b52ff151 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/ManageWindowsViewContainer.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/multiinstance/ManageWindowsViewContainer.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.shared.desktopmode
+package com.android.wm.shell.shared.multiinstance
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.AnimatorSet
@@ -34,6 +34,7 @@ import android.view.View.SCALE_Y
import android.view.ViewGroup.MarginLayoutParams
import android.widget.LinearLayout
import android.window.TaskSnapshot
+import com.android.wm.shell.shared.R
/**
* View for the All Windows menu option, used by both Desktop Windowing and Taskbar.
@@ -167,6 +168,9 @@ abstract class ManageWindowsViewContainer(
val appSnapshotButton = SurfaceView(context)
appSnapshotButton.cornerRadius = iconRadius
appSnapshotButton.setZOrderOnTop(true)
+ appSnapshotButton.contentDescription = context.resources.getString(
+ R.string.manage_windows_icon_text, iconCount + 1
+ )
appSnapshotButton.setOnClickListener {
onIconClickListener?.invoke(taskId)
}
@@ -230,10 +234,12 @@ abstract class ManageWindowsViewContainer(
/** Play the animation for opening the menu. */
fun animateOpen() {
animateView(rootView, MENU_BOUNDS_SHRUNK_SCALE, MENU_BOUNDS_FULL_SCALE,
- MENU_START_ALPHA, MENU_FULL_ALPHA)
+ MENU_START_ALPHA, MENU_FULL_ALPHA
+ )
for (view in iconViews) {
animateView(view, MENU_BOUNDS_SHRUNK_SCALE, MENU_BOUNDS_FULL_SCALE,
- MENU_START_ALPHA, MENU_FULL_ALPHA)
+ MENU_START_ALPHA, MENU_FULL_ALPHA
+ )
}
createAnimatorSet().start()
}
@@ -241,10 +247,12 @@ abstract class ManageWindowsViewContainer(
/** Play the animation for closing the menu. */
fun animateClose(callback: () -> Unit) {
animateView(rootView, MENU_BOUNDS_FULL_SCALE, MENU_BOUNDS_SHRUNK_SCALE,
- MENU_FULL_ALPHA, MENU_START_ALPHA)
+ MENU_FULL_ALPHA, MENU_START_ALPHA
+ )
for (view in iconViews) {
animateView(view, MENU_BOUNDS_FULL_SCALE, MENU_BOUNDS_SHRUNK_SCALE,
- MENU_FULL_ALPHA, MENU_START_ALPHA)
+ MENU_FULL_ALPHA, MENU_START_ALPHA
+ )
}
createAnimatorSet().apply {
addListener(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
index 068b2d246500..0fd4206c0545 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
@@ -101,9 +101,13 @@ public class BubblePositioner {
private int mBubbleBarTopOnScreen;
public BubblePositioner(Context context, WindowManager windowManager) {
+ this(context, DeviceConfig.create(context, windowManager));
+ }
+
+ public BubblePositioner(Context context, DeviceConfig deviceConfig) {
mContext = context;
- mDeviceConfig = DeviceConfig.create(context, windowManager);
- update(mDeviceConfig);
+ mDeviceConfig = deviceConfig;
+ update(deviceConfig);
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/transition/TransitionStateHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/transition/TransitionStateHolder.kt
new file mode 100644
index 000000000000..4dda2a894253
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/transition/TransitionStateHolder.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common.transition
+
+import com.android.wm.shell.dagger.WMSingleton
+import com.android.wm.shell.recents.RecentsTransitionHandler
+import com.android.wm.shell.recents.RecentsTransitionStateListener
+import com.android.wm.shell.recents.RecentsTransitionStateListener.RecentsTransitionState
+import com.android.wm.shell.recents.RecentsTransitionStateListener.isRunning
+import com.android.wm.shell.sysui.ShellInit
+import javax.inject.Inject
+
+/**
+ * Holder for the state of the transitions.
+ */
+@WMSingleton
+class TransitionStateHolder @Inject constructor(
+ shellInit: ShellInit,
+ private val recentsTransitionHandler: RecentsTransitionHandler
+) {
+
+ @Volatile
+ @RecentsTransitionState
+ private var recentsTransitionState: Int =
+ RecentsTransitionStateListener.TRANSITION_STATE_NOT_RUNNING
+
+ init {
+ shellInit.addInitCallback({ onInit() }, this)
+ }
+
+ fun isRecentsTransitionRunning(): Boolean = isRunning(recentsTransitionState)
+
+ private fun onInit() {
+ recentsTransitionHandler.addTransitionStateListener(
+ object : RecentsTransitionStateListener {
+ override fun onTransitionStateChanged(@RecentsTransitionState state: Int) {
+ recentsTransitionState = state
+ }
+ }
+ )
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUILayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUILayout.java
index 49c2785f1ecd..688f8ca2dc75 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUILayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUILayout.java
@@ -68,7 +68,7 @@ class CompatUILayout extends LinearLayout {
private void setViewVisibility(@IdRes int resId, boolean show) {
final View view = findViewById(resId);
- int visibility = show ? View.VISIBLE : View.INVISIBLE;
+ int visibility = show ? View.VISIBLE : View.GONE;
if (view.getVisibility() == visibility) {
return;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayout.java
index fd1bbc477cf4..b141bebbe8b1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayout.java
@@ -100,7 +100,7 @@ public class UserAspectRatioSettingsLayout extends LinearLayout {
private void setViewVisibility(@IdRes int resId, boolean show) {
final View view = findViewById(resId);
- int visibility = show ? View.VISIBLE : View.INVISIBLE;
+ int visibility = show ? View.VISIBLE : View.GONE;
if (view.getVisibility() == visibility) {
return;
}
@@ -171,7 +171,7 @@ public class UserAspectRatioSettingsLayout extends LinearLayout {
fadeOut.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- view.setVisibility(View.INVISIBLE);
+ view.setVisibility(View.GONE);
}
});
fadeOut.start();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxController.kt
index 0ac7aff306a0..523e2f5cf7dc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxController.kt
@@ -18,133 +18,51 @@ package com.android.wm.shell.compatui.letterbox
import android.graphics.Rect
import android.view.SurfaceControl
-import com.android.internal.protolog.ProtoLog
-import com.android.wm.shell.dagger.WMSingleton
-import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_APP_COMPAT
-import javax.inject.Inject
+import android.view.SurfaceControl.Transaction
/**
- * Component responsible for handling the lifecycle of the letterbox surfaces.
+ * Abstracts the component responsible to handle a single or multiple letterbox surfaces for a
+ * specific [Change].
*/
-@WMSingleton
-class LetterboxController @Inject constructor(
- private val letterboxConfiguration: LetterboxConfiguration
-) {
-
- companion object {
- /*
- * Letterbox surfaces need to stay below the activity layer which is 0.
- */
- // TODO(b/378673153): Consider adding this to [TaskConstants].
- @JvmStatic
- private val TASK_CHILD_LAYER_LETTERBOX_BACKGROUND = -1000
- @JvmStatic
- private val TAG = "LetterboxController"
- }
-
- private val letterboxMap = mutableMapOf<LetterboxKey, LetterboxItem>()
+interface LetterboxController {
/**
* Creates a Letterbox Surface for a given displayId/taskId if it doesn't exist.
*/
fun createLetterboxSurface(
key: LetterboxKey,
- startTransaction: SurfaceControl.Transaction,
+ transaction: Transaction,
parentLeash: SurfaceControl
- ) {
- letterboxMap.runOnItem(key, onMissed = { k, m ->
- m[k] = LetterboxItem(
- SurfaceControl.Builder()
- .setName("ShellLetterboxSurface-$key")
- .setHidden(true)
- .setColorLayer()
- .setParent(parentLeash)
- .setCallsite("LetterboxController-createLetterboxSurface")
- .build().apply {
- startTransaction.setLayer(
- this,
- TASK_CHILD_LAYER_LETTERBOX_BACKGROUND
- ).setColorSpaceAgnostic(this, true)
- .setColor(this, letterboxConfiguration.getBackgroundColorRgbArray())
- }
- )
- })
- }
+ )
/**
* Invoked to destroy the surfaces for a letterbox session for given displayId/taskId.
*/
fun destroyLetterboxSurface(
key: LetterboxKey,
- startTransaction: SurfaceControl.Transaction
- ) {
- letterboxMap.runOnItem(key, onFound = { item ->
- item.fullWindowSurface?.run {
- startTransaction.remove(this)
- }
- })
- letterboxMap.remove(key)
- }
+ transaction: Transaction
+ )
/**
* Invoked to show/hide the letterbox surfaces for given displayId/taskId.
*/
fun updateLetterboxSurfaceVisibility(
key: LetterboxKey,
- startTransaction: SurfaceControl.Transaction,
- visible: Boolean = true
- ) {
- letterboxMap.runOnItem(key, onFound = { item ->
- item.fullWindowSurface?.run {
- startTransaction.setVisibility(this, visible)
- }
- })
- }
+ transaction: Transaction,
+ visible: Boolean
+ )
/**
* Updates the bounds for the letterbox surfaces for given displayId/taskId.
*/
fun updateLetterboxSurfaceBounds(
key: LetterboxKey,
- startTransaction: SurfaceControl.Transaction,
- bounds: Rect
- ) {
- letterboxMap.runOnItem(key, onFound = { item ->
- item.fullWindowSurface?.run {
- startTransaction.moveAndCrop(this, bounds)
- }
- })
- }
+ transaction: Transaction,
+ taskBounds: Rect
+ )
- /*
- * Executes [onFound] on the [LetterboxItem] if present or [onMissed] if not present.
+ /**
+ * Utility method to dump the current state.
*/
- private fun MutableMap<LetterboxKey, LetterboxItem>.runOnItem(
- key: LetterboxKey,
- onFound: (LetterboxItem) -> Unit = { _ -> },
- onMissed: (
- LetterboxKey,
- MutableMap<LetterboxKey, LetterboxItem>
- ) -> Unit = { _, _ -> }
- ) {
- this[key]?.let {
- return onFound(it)
- }
- return onMissed(key, this)
- }
-
- fun dump() {
- ProtoLog.v(WM_SHELL_APP_COMPAT, "%s: %s", TAG, "${letterboxMap.keys}")
- }
-
- private fun SurfaceControl.Transaction.moveAndCrop(
- surface: SurfaceControl,
- rect: Rect
- ): SurfaceControl.Transaction =
- setPosition(surface, rect.left.toFloat(), rect.top.toFloat())
- .setWindowCrop(
- surface,
- rect.width(),
- rect.height()
- )
+ fun dump()
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxData.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxData.kt
index 98fd2472f1e4..adb034cc4787 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxData.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxData.kt
@@ -16,10 +16,5 @@
package com.android.wm.shell.compatui.letterbox
-import android.view.SurfaceControl
-
// The key to use for identify the letterbox sessions.
-data class LetterboxKey(val displayId: Int, val taskId: Int)
-
-// Encapsulate the objects for the specific letterbox session.
-data class LetterboxItem(val fullWindowSurface: SurfaceControl?) \ No newline at end of file
+data class LetterboxKey(val displayId: Int, val taskId: Int) \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxSurfaceBuilder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxSurfaceBuilder.kt
new file mode 100644
index 000000000000..e88d91f3956c
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxSurfaceBuilder.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui.letterbox
+
+import android.view.SurfaceControl
+import com.android.wm.shell.dagger.WMSingleton
+import javax.inject.Inject
+
+/**
+ * Component responsible for the actual creation of the Letterbox surfaces.
+ */
+@WMSingleton
+class LetterboxSurfaceBuilder @Inject constructor(
+ private val letterboxConfiguration: LetterboxConfiguration
+) {
+
+ companion object {
+ /*
+ * Letterbox surfaces need to stay below the activity layer which is 0.
+ */
+ // TODO(b/378673153): Consider adding this to [TaskConstants].
+ @JvmStatic
+ private val TASK_CHILD_LAYER_LETTERBOX_BACKGROUND = -1000
+ }
+
+ fun createSurface(
+ tx: SurfaceControl.Transaction,
+ parentLeash: SurfaceControl,
+ surfaceName: String,
+ callSite: String,
+ surfaceBuilder: SurfaceControl.Builder = SurfaceControl.Builder()
+ ) = surfaceBuilder
+ .setName(surfaceName)
+ .setHidden(true)
+ .setColorLayer()
+ .setParent(parentLeash)
+ .setCallsite(callSite)
+ .build().apply {
+ tx.setLayer(
+ this,
+ TASK_CHILD_LAYER_LETTERBOX_BACKGROUND
+ ).setColorSpaceAgnostic(this, true)
+ .setColor(this, letterboxConfiguration.getBackgroundColorRgbArray())
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserver.kt
index 67429bdd112b..b50716ad07a3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserver.kt
@@ -43,12 +43,7 @@ class LetterboxTransitionObserver(
init {
if (appCompatRefactoring()) {
- ProtoLog.v(
- WM_SHELL_APP_COMPAT,
- "%s: %s",
- TAG,
- "Initializing LetterboxTransitionObserver"
- )
+ logV("Initializing LetterboxTransitionObserver")
shellInit.addInitCallback({
transitions.registerObserver(this)
}, this)
@@ -69,38 +64,45 @@ class LetterboxTransitionObserver(
for (change in info.changes) {
change.taskInfo?.let { ti ->
val key = LetterboxKey(ti.displayId, ti.taskId)
- if (isClosingType(change.mode)) {
- letterboxController.destroyLetterboxSurface(
- key,
- startTransaction
- )
- } else {
- val isTopActivityLetterboxed = ti.appCompatTaskInfo.isTopActivityLetterboxed
- if (isTopActivityLetterboxed) {
- letterboxController.createLetterboxSurface(
+ val taskBounds = Rect(
+ change.endRelOffset.x,
+ change.endRelOffset.y,
+ change.endAbsBounds.width(),
+ change.endAbsBounds.height()
+ )
+ with(letterboxController) {
+ if (isClosingType(change.mode)) {
+ destroyLetterboxSurface(
key,
- startTransaction,
- change.leash
+ startTransaction
)
- letterboxController.updateLetterboxSurfaceBounds(
+ } else {
+ val isTopActivityLetterboxed = ti.appCompatTaskInfo.isTopActivityLetterboxed
+ if (isTopActivityLetterboxed) {
+ createLetterboxSurface(
+ key,
+ startTransaction,
+ change.leash
+ )
+ updateLetterboxSurfaceBounds(
+ key,
+ startTransaction,
+ taskBounds
+ )
+ }
+ updateLetterboxSurfaceVisibility(
key,
startTransaction,
- Rect(
- change.endRelOffset.x,
- change.endRelOffset.y,
- change.endAbsBounds.width(),
- change.endAbsBounds.height()
- )
+ isTopActivityLetterboxed
)
}
- letterboxController.updateLetterboxSurfaceVisibility(
- key,
- startTransaction,
- isTopActivityLetterboxed
- )
+ dump()
}
- letterboxController.dump()
}
}
}
+
+ private fun logV(msg: String) {
+ ProtoLog.v(WM_SHELL_APP_COMPAT, "%s: %s", TAG, msg)
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/SingleSurfaceLetterboxController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/SingleSurfaceLetterboxController.kt
new file mode 100644
index 000000000000..f21a7272287e
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterbox/SingleSurfaceLetterboxController.kt
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui.letterbox
+
+import android.graphics.Rect
+import android.view.SurfaceControl
+import android.view.SurfaceControl.Transaction
+import com.android.internal.protolog.ProtoLog
+import com.android.wm.shell.dagger.WMSingleton
+import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_APP_COMPAT
+import javax.inject.Inject
+
+/**
+ * Component responsible for handling the lifecycle of a single letterbox surface.
+ */
+@WMSingleton
+class SingleSurfaceLetterboxController @Inject constructor(
+ private val letterboxBuilder: LetterboxSurfaceBuilder
+) : LetterboxController {
+
+ companion object {
+ @JvmStatic
+ private val TAG = "LetterboxController"
+ }
+
+ private val letterboxMap = mutableMapOf<LetterboxKey, SurfaceControl>()
+
+ /**
+ * Creates a Letterbox Surface for a given displayId/taskId if it doesn't exist.
+ */
+ override fun createLetterboxSurface(
+ key: LetterboxKey,
+ transaction: Transaction,
+ parentLeash: SurfaceControl
+ ) {
+ letterboxMap.runOnItem(key, onMissed = { k, m ->
+ m[k] = letterboxBuilder.createSurface(
+ transaction,
+ parentLeash,
+ surfaceName = "ShellLetterboxSurface-$key",
+ callSite = "LetterboxController-createLetterboxSurface"
+ )
+ })
+ }
+
+ /**
+ * Invoked to destroy the surfaces for a letterbox session for given displayId/taskId.
+ */
+ override fun destroyLetterboxSurface(
+ key: LetterboxKey,
+ transaction: Transaction
+ ) {
+ letterboxMap.runOnItem(key, onFound = { item ->
+ item.run {
+ transaction.remove(this)
+ }
+ })
+ letterboxMap.remove(key)
+ }
+
+ /**
+ * Invoked to show/hide the letterbox surfaces for given displayId/taskId.
+ */
+ override fun updateLetterboxSurfaceVisibility(
+ key: LetterboxKey,
+ transaction: Transaction,
+ visible: Boolean
+ ) {
+ letterboxMap.runOnItem(key, onFound = { item ->
+ item.run {
+ transaction.setVisibility(this, visible)
+ }
+ })
+ }
+
+ /**
+ * Updates the bounds for the letterbox surfaces for given displayId/taskId.
+ */
+ override fun updateLetterboxSurfaceBounds(
+ key: LetterboxKey,
+ transaction: Transaction,
+ taskBounds: Rect
+ ) {
+ letterboxMap.runOnItem(key, onFound = { item ->
+ item.run {
+ transaction.moveAndCrop(this, taskBounds)
+ }
+ })
+ }
+
+ override fun dump() {
+ ProtoLog.v(WM_SHELL_APP_COMPAT, "%s: %s", TAG, "${letterboxMap.keys}")
+ }
+
+ /*
+ * Executes [onFound] on the [SurfaceControl] if present or [onMissed] if not present.
+ */
+ private fun MutableMap<LetterboxKey, SurfaceControl>.runOnItem(
+ key: LetterboxKey,
+ onFound: (SurfaceControl) -> Unit = { _ -> },
+ onMissed: (
+ LetterboxKey,
+ MutableMap<LetterboxKey, SurfaceControl>
+ ) -> Unit = { _, _ -> }
+ ) {
+ this[key]?.let {
+ return onFound(it)
+ }
+ return onMissed(key, this)
+ }
+
+ private fun Transaction.moveAndCrop(
+ surface: SurfaceControl,
+ rect: Rect
+ ): Transaction =
+ setPosition(surface, rect.left.toFloat(), rect.top.toFloat())
+ .setWindowCrop(
+ surface,
+ rect.width(),
+ rect.height()
+ )
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 60237502007d..cb9c20e9b7ec 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -799,10 +799,11 @@ public abstract class WMShellBaseModule {
Transitions transitions,
TaskStackListenerImpl taskStackListener,
@ShellMainThread Handler mainHandler,
- @ShellMainThread ShellExecutor mainExecutor) {
+ @ShellMainThread ShellExecutor mainExecutor,
+ FocusTransitionObserver focusTransitionObserver) {
return new KeyguardTransitionHandler(
shellInit, shellController, displayController, transitions, taskStackListener,
- mainHandler, mainExecutor);
+ mainHandler, mainExecutor, focusTransitionObserver);
}
@WMSingleton
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 0f636588476a..974535385334 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
@@ -71,6 +71,7 @@ import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.compatui.letterbox.LetterboxCommandHandler;
import com.android.wm.shell.compatui.letterbox.LetterboxController;
import com.android.wm.shell.compatui.letterbox.LetterboxTransitionObserver;
+import com.android.wm.shell.compatui.letterbox.SingleSurfaceLetterboxController;
import com.android.wm.shell.dagger.back.ShellBackAnimationModule;
import com.android.wm.shell.dagger.pip.PipModule;
import com.android.wm.shell.desktopmode.CloseDesktopTaskTransitionHandler;
@@ -854,14 +855,15 @@ public abstract class WMShellModule {
Optional<DesktopTasksController> desktopTasksController,
InputManager inputManager,
ShellTaskOrganizer shellTaskOrganizer,
- FocusTransitionObserver focusTransitionObserver) {
+ FocusTransitionObserver focusTransitionObserver,
+ @ShellMainThread ShellExecutor mainExecutor) {
if (DesktopModeStatus.canEnterDesktopMode(context) && useKeyGestureEventHandler()
&& manageKeyGestures()
&& (Flags.enableMoveToNextDisplayShortcut()
|| Flags.enableTaskResizingKeyboardShortcuts())) {
return Optional.of(new DesktopModeKeyGestureHandler(context,
desktopModeWindowDecorViewModel, desktopTasksController,
- inputManager, shellTaskOrganizer, focusTransitionObserver));
+ inputManager, shellTaskOrganizer, focusTransitionObserver, mainExecutor));
}
return Optional.empty();
}
@@ -885,6 +887,7 @@ public abstract class WMShellModule {
SyncTransactionQueue syncQueue,
Transitions transitions,
Optional<DesktopTasksController> desktopTasksController,
+ Optional<DesktopImmersiveController> desktopImmersiveController,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
InteractionJankMonitor interactionJankMonitor,
AppToWebGenericLinksParser genericLinksParser,
@@ -906,6 +909,7 @@ public abstract class WMShellModule {
mainChoreographer, bgExecutor, shellInit, shellCommandHandler, windowManager,
taskOrganizer, desktopRepository, displayController, shellController,
displayInsetsController, syncQueue, transitions, desktopTasksController,
+ desktopImmersiveController.get(),
rootTaskDisplayAreaOrganizer, interactionJankMonitor, genericLinksParser,
assistContentRequester, multiInstanceHelper, desktopTasksLimiter,
appHandleEducationController, appToWebEducationController,
@@ -1316,4 +1320,9 @@ public abstract class WMShellModule {
) {
return new LetterboxTransitionObserver(shellInit, transitions, letterboxController);
}
+
+ @WMSingleton
+ @Binds
+ abstract LetterboxController bindsLetterboxController(
+ SingleSurfaceLetterboxController letterboxController);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt
index 4723eb273988..dd95273dd4f3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt
@@ -99,6 +99,7 @@ class DesktopImmersiveController(
/** Starts a transition to enter full immersive state inside the desktop. */
fun moveTaskToImmersive(taskInfo: RunningTaskInfo) {
+ check(taskInfo.isFreeform) { "Task must already be in freeform" }
if (inProgress) {
logV(
"Cannot start entry because transition(s) already in progress: %s",
@@ -121,6 +122,7 @@ class DesktopImmersiveController(
/** Starts a transition to move an immersive task out of immersive. */
fun moveTaskToNonImmersive(taskInfo: RunningTaskInfo, reason: ExitReason) {
+ check(taskInfo.isFreeform) { "Task must already be in freeform" }
if (inProgress) {
logV(
"Cannot start exit because transition(s) already in progress: %s",
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt
index 6cb23b81f1e4..2b0724d64d0e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt
@@ -30,9 +30,11 @@ import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.content.Context
import com.android.hardware.input.Flags.manageKeyGestures
import com.android.window.flags.Flags.enableTaskResizingKeyboardShortcuts
+import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger
import com.android.wm.shell.transition.FocusTransitionObserver
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
+import com.android.wm.shell.shared.annotations.ShellMainThread
import java.util.Optional
/**
@@ -45,6 +47,7 @@ class DesktopModeKeyGestureHandler(
inputManager: InputManager,
private val shellTaskOrganizer: ShellTaskOrganizer,
private val focusTransitionObserver: FocusTransitionObserver,
+ @ShellMainThread private val mainExecutor: ShellExecutor,
) : KeyGestureEventHandler {
init {
@@ -72,7 +75,8 @@ class DesktopModeKeyGestureHandler(
desktopModeWindowDecorViewModel.get().onSnapResize(
it.taskId,
true,
- DesktopModeEventLogger.Companion.InputMethod.KEYBOARD
+ DesktopModeEventLogger.Companion.InputMethod.KEYBOARD,
+ /* fromMenu= */ false
)
}
return true
@@ -83,7 +87,8 @@ class DesktopModeKeyGestureHandler(
desktopModeWindowDecorViewModel.get().onSnapResize(
it.taskId,
false,
- DesktopModeEventLogger.Companion.InputMethod.KEYBOARD
+ DesktopModeEventLogger.Companion.InputMethod.KEYBOARD,
+ /* fromMenu= */ false
)
}
return true
@@ -102,9 +107,11 @@ class DesktopModeKeyGestureHandler(
KeyGestureEvent.KEY_GESTURE_TYPE_MINIMIZE_FREEFORM_WINDOW -> {
logV("Key gesture MINIMIZE_FREEFORM_WINDOW is handled")
getGloballyFocusedFreeformTask()?.let {
- desktopTasksController.get().minimizeTask(
- it,
- )
+ mainExecutor.execute {
+ desktopTasksController.get().minimizeTask(
+ it,
+ )
+ }
}
return true
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLogger.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLogger.kt
index d0e01625a3aa..2c432bcb55ab 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLogger.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLogger.kt
@@ -124,7 +124,19 @@ class DesktopModeUiEventLogger(
@UiEvent(doc = "Drag the window header to an edge to tile it to the left side")
DESKTOP_WINDOW_APP_HEADER_DRAG_TO_TILE_TO_LEFT(2006),
@UiEvent(doc = "Drag the window header to an edge to tile it to the right side")
- DESKTOP_WINDOW_APP_HEADER_DRAG_TO_TILE_TO_RIGHT(2007);
+ DESKTOP_WINDOW_APP_HEADER_DRAG_TO_TILE_TO_RIGHT(2007),
+ @UiEvent(doc = "Hover or long press the maximize button to reveal the menu")
+ DESKTOP_WINDOW_MAXIMIZE_BUTTON_REVEAL_MENU(2015),
+ @UiEvent(doc = "Tap on the maximize option in the maximize button menu")
+ DESKTOP_WINDOW_MAXIMIZE_BUTTON_MENU_TAP_TO_MAXIMIZE(2009),
+ @UiEvent(doc = "Tap on the immersive option in the maximize button menu")
+ DESKTOP_WINDOW_MAXIMIZE_BUTTON_MENU_TAP_TO_IMMERSIVE(2010),
+ @UiEvent(doc = "Tap on the restore option in the maximize button menu")
+ DESKTOP_WINDOW_MAXIMIZE_BUTTON_MENU_TAP_TO_RESTORE(2011),
+ @UiEvent(doc = "Tap on the tile to left option in the maximize button menu")
+ DESKTOP_WINDOW_MAXIMIZE_BUTTON_MENU_TAP_TO_TILE_TO_LEFT(2012),
+ @UiEvent(doc = "Tap on the tile to right option in the maximize button menu")
+ DESKTOP_WINDOW_MAXIMIZE_BUTTON_MENU_TAP_TO_TILE_TO_RIGHT(2013);
override fun getId(): Int = mId
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 8bc597e07c52..c479ab382acb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -788,31 +788,6 @@ class DesktopTasksController(
transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */)
}
- /** Moves a task in/out of full immersive state within the desktop. */
- fun toggleDesktopTaskFullImmersiveState(taskInfo: RunningTaskInfo) {
- if (taskRepository.isTaskInFullImmersiveState(taskInfo.taskId)) {
- exitDesktopTaskFromFullImmersive(
- taskInfo,
- DesktopImmersiveController.ExitReason.USER_INTERACTION,
- )
- } else {
- moveDesktopTaskToFullImmersive(taskInfo)
- }
- }
-
- private fun moveDesktopTaskToFullImmersive(taskInfo: RunningTaskInfo) {
- check(taskInfo.isFreeform) { "Task must already be in freeform" }
- desktopImmersiveController.moveTaskToImmersive(taskInfo)
- }
-
- private fun exitDesktopTaskFromFullImmersive(
- taskInfo: RunningTaskInfo,
- reason: DesktopImmersiveController.ExitReason,
- ) {
- check(taskInfo.isFreeform) { "Task must already be in freeform" }
- desktopImmersiveController.moveTaskToNonImmersive(taskInfo, reason)
- }
-
/**
* Quick-resizes a desktop task, toggling between a fullscreen state (represented by the stable
* bounds) and a free floating state (either the last saved bounds if available or the default
@@ -2379,7 +2354,7 @@ class DesktopTasksController(
if (inImmersive && !requestingImmersive
&& !RecentsTransitionStateListener.isRunning(recentsTransitionState)) {
// Exit immersive if the app is no longer requesting it.
- exitDesktopTaskFromFullImmersive(
+ desktopImmersiveController.moveTaskToNonImmersive(
taskInfo,
DesktopImmersiveController.ExitReason.APP_NOT_IMMERSIVE
)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationController.kt
index de9c79ab34fd..c5fca028a1a6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationController.kt
@@ -36,8 +36,8 @@ import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource
import com.android.wm.shell.windowdecor.common.DecorThemeUtil
import com.android.wm.shell.windowdecor.common.Theme
import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationTooltipController
-import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationTooltipController.TooltipEducationViewConfig
import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationTooltipController.TooltipColorScheme
+import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationTooltipController.TooltipEducationViewConfig
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.MainCoroutineDispatcher
@@ -74,293 +74,330 @@ class AppHandleEducationController(
@ShellMainThread private val applicationCoroutineScope: CoroutineScope,
@ShellBackgroundThread private val backgroundDispatcher: MainCoroutineDispatcher,
) {
- private val decorThemeUtil = DecorThemeUtil(context)
- private lateinit var openHandleMenuCallback: (Int) -> Unit
- private lateinit var toDesktopModeCallback: (Int, DesktopModeTransitionSource) -> Unit
+ private val decorThemeUtil = DecorThemeUtil(context)
+ private lateinit var openHandleMenuCallback: (Int) -> Unit
+ private lateinit var toDesktopModeCallback: (Int, DesktopModeTransitionSource) -> Unit
+
+ init {
+ runIfEducationFeatureEnabled {
+ applicationCoroutineScope.launch {
+ // Central block handling the app handle's educational flow end-to-end.
+ isAppHandleHintViewedFlow()
+ .flatMapLatest { isAppHandleHintViewed ->
+ if (isAppHandleHintViewed) {
+ // If the education is viewed then return emptyFlow() that completes
+ // immediately.
+ // This will help us to not listen to [captionHandleStateFlow] after the
+ // education
+ // has been viewed already.
+ emptyFlow()
+ } else {
+ // Listen for changes to window decor's caption handle.
+ windowDecorCaptionHandleRepository.captionStateFlow
+ // Wait for few seconds before emitting the latest state.
+ .debounce(APP_HANDLE_EDUCATION_DELAY_MILLIS)
+ .filter { captionState ->
+ captionState is CaptionState.AppHandle &&
+ appHandleEducationFilter.shouldShowAppHandleEducation(
+ captionState
+ )
+ }
+ }
+ }
+ .flowOn(backgroundDispatcher)
+ .collectLatest { captionState ->
+ val tooltipColorScheme = tooltipColorScheme(captionState)
+
+ showEducation(captionState, tooltipColorScheme)
+ // After showing first tooltip, mark education as viewed
+ appHandleEducationDatastoreRepository
+ .updateAppHandleHintViewedTimestampMillis(true)
+ }
+ }
- init {
- runIfEducationFeatureEnabled {
- applicationCoroutineScope.launch {
- // Central block handling the app handle's educational flow end-to-end.
- isAppHandleHintViewedFlow()
- .flatMapLatest { isAppHandleHintViewed ->
- if (isAppHandleHintViewed) {
- // If the education is viewed then return emptyFlow() that completes immediately.
- // This will help us to not listen to [captionHandleStateFlow] after the education
- // has been viewed already.
- emptyFlow()
- } else {
- // Listen for changes to window decor's caption handle.
+ applicationCoroutineScope.launch {
+ if (isAppHandleHintUsed()) return@launch
windowDecorCaptionHandleRepository.captionStateFlow
- // Wait for few seconds before emitting the latest state.
- .debounce(APP_HANDLE_EDUCATION_DELAY_MILLIS)
.filter { captionState ->
- captionState is CaptionState.AppHandle &&
- appHandleEducationFilter.shouldShowAppHandleEducation(captionState)
+ captionState is CaptionState.AppHandle && captionState.isHandleMenuExpanded
}
- }
+ .take(1)
+ .flowOn(backgroundDispatcher)
+ .collect {
+ // If user expands app handle, mark user has used the app handle hint
+ appHandleEducationDatastoreRepository
+ .updateAppHandleHintUsedTimestampMillis(true)
+ }
+ }
+ }
+ }
+
+ private inline fun runIfEducationFeatureEnabled(block: () -> Unit) {
+ if (canEnterDesktopMode(context) && Flags.enableDesktopWindowingAppHandleEducation())
+ block()
+ }
+
+ private fun showEducation(captionState: CaptionState, tooltipColorScheme: TooltipColorScheme) {
+ val appHandleBounds = (captionState as CaptionState.AppHandle).globalAppHandleBounds
+ val tooltipGlobalCoordinates =
+ Point(appHandleBounds.left + appHandleBounds.width() / 2, appHandleBounds.bottom)
+ // TODO: b/370546801 - Differentiate between user dismissing the tooltip vs following the
+ // cue.
+ // Populate information important to inflate app handle education tooltip.
+ val appHandleTooltipConfig =
+ TooltipEducationViewConfig(
+ tooltipViewLayout = R.layout.desktop_windowing_education_top_arrow_tooltip,
+ tooltipColorScheme = tooltipColorScheme,
+ tooltipViewGlobalCoordinates = tooltipGlobalCoordinates,
+ tooltipText = getString(R.string.windowing_app_handle_education_tooltip),
+ arrowDirection =
+ DesktopWindowingEducationTooltipController.TooltipArrowDirection.UP,
+ onEducationClickAction = {
+ launchWithExceptionHandling {
+ showWindowingImageButtonTooltip(tooltipColorScheme)
+ }
+ openHandleMenuCallback(captionState.runningTaskInfo.taskId)
+ },
+ onDismissAction = {
+ launchWithExceptionHandling {
+ showWindowingImageButtonTooltip(tooltipColorScheme)
+ }
+ },
+ )
+
+ windowingEducationViewController.showEducationTooltip(
+ tooltipViewConfig = appHandleTooltipConfig,
+ taskId = captionState.runningTaskInfo.taskId,
+ )
+ }
+
+ /** Show tooltip that points to windowing image button in app handle menu */
+ private suspend fun showWindowingImageButtonTooltip(tooltipColorScheme: TooltipColorScheme) {
+ val appInfoPillHeight = getSize(R.dimen.desktop_mode_handle_menu_app_info_pill_height)
+ val windowingOptionPillHeight =
+ getSize(R.dimen.desktop_mode_handle_menu_windowing_pill_height)
+ val appHandleMenuWidth =
+ getSize(R.dimen.desktop_mode_handle_menu_width) +
+ getSize(R.dimen.desktop_mode_handle_menu_pill_spacing_margin)
+ val appHandleMenuMargins =
+ getSize(R.dimen.desktop_mode_handle_menu_margin_top) +
+ getSize(R.dimen.desktop_mode_handle_menu_pill_spacing_margin)
+
+ windowDecorCaptionHandleRepository.captionStateFlow
+ // After the first tooltip was dismissed, wait for 400 ms and see if the app handle menu
+ // has been expanded.
+ .timeout(APP_HANDLE_EDUCATION_TIMEOUT_MILLIS.milliseconds)
+ .catchTimeoutAndLog {
+ // TODO: b/341320146 - Log previous tooltip was dismissed
+ }
+ // Wait for few milliseconds before emitting the latest state.
+ .debounce(APP_HANDLE_EDUCATION_DELAY_MILLIS)
+ .filter { captionState ->
+ // Filter out states when app handle is not visible or not expanded.
+ captionState is CaptionState.AppHandle && captionState.isHandleMenuExpanded
}
+ // Before showing this tooltip, stop listening to further emissions to avoid
+ // accidentally
+ // showing the same tooltip on future emissions.
+ .take(1)
.flowOn(backgroundDispatcher)
.collectLatest { captionState ->
- val tooltipColorScheme = tooltipColorScheme(captionState)
+ captionState as CaptionState.AppHandle
+ val appHandleBounds = captionState.globalAppHandleBounds
+ val tooltipGlobalCoordinates =
+ Point(
+ appHandleBounds.left + appHandleBounds.width() / 2 + appHandleMenuWidth / 2,
+ appHandleBounds.top +
+ appHandleMenuMargins +
+ appInfoPillHeight +
+ windowingOptionPillHeight / 2,
+ )
+ // Populate information important to inflate windowing image button education
+ // tooltip.
+ val windowingImageButtonTooltipConfig =
+ TooltipEducationViewConfig(
+ tooltipViewLayout = R.layout.desktop_windowing_education_left_arrow_tooltip,
+ tooltipColorScheme = tooltipColorScheme,
+ tooltipViewGlobalCoordinates = tooltipGlobalCoordinates,
+ tooltipText =
+ getString(
+ R.string.windowing_desktop_mode_image_button_education_tooltip
+ ),
+ arrowDirection =
+ DesktopWindowingEducationTooltipController.TooltipArrowDirection.LEFT,
+ onEducationClickAction = {
+ launchWithExceptionHandling {
+ showExitWindowingTooltip(tooltipColorScheme)
+ }
+ toDesktopModeCallback(
+ captionState.runningTaskInfo.taskId,
+ DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON,
+ )
+ },
+ onDismissAction = {
+ launchWithExceptionHandling {
+ showExitWindowingTooltip(tooltipColorScheme)
+ }
+ },
+ )
- showEducation(captionState, tooltipColorScheme)
- // After showing first tooltip, mark education as viewed
- appHandleEducationDatastoreRepository.updateAppHandleHintViewedTimestampMillis(true)
+ windowingEducationViewController.showEducationTooltip(
+ taskId = captionState.runningTaskInfo.taskId,
+ tooltipViewConfig = windowingImageButtonTooltipConfig,
+ )
}
- }
+ }
- applicationCoroutineScope.launch {
- if (isAppHandleHintUsed()) return@launch
+ /** Show tooltip that points to app chip button and educates user on how to exit desktop mode */
+ private suspend fun showExitWindowingTooltip(tooltipColorScheme: TooltipColorScheme) {
windowDecorCaptionHandleRepository.captionStateFlow
+ // After the previous tooltip was dismissed, wait for 400 ms and see if the user entered
+ // desktop mode.
+ .timeout(APP_HANDLE_EDUCATION_TIMEOUT_MILLIS.milliseconds)
+ .catchTimeoutAndLog {
+ // TODO: b/341320146 - Log previous tooltip was dismissed
+ }
+ // Wait for few milliseconds before emitting the latest state.
+ .debounce(APP_HANDLE_EDUCATION_DELAY_MILLIS)
.filter { captionState ->
- captionState is CaptionState.AppHandle && captionState.isHandleMenuExpanded
+ // Filter out states when app header is not visible or expanded.
+ captionState is CaptionState.AppHeader && !captionState.isHeaderMenuExpanded
}
+ // Before showing this tooltip, stop listening to further emissions to avoid
+ // accidentally
+ // showing the same tooltip on future emissions.
.take(1)
.flowOn(backgroundDispatcher)
- .collect {
- // If user expands app handle, mark user has used the app handle hint
- appHandleEducationDatastoreRepository.updateAppHandleHintUsedTimestampMillis(true)
+ .collectLatest { captionState ->
+ captionState as CaptionState.AppHeader
+ val globalAppChipBounds = captionState.globalAppChipBounds
+ val tooltipGlobalCoordinates =
+ Point(
+ globalAppChipBounds.right,
+ globalAppChipBounds.top + globalAppChipBounds.height() / 2,
+ )
+ // Populate information important to inflate exit desktop mode education tooltip.
+ val exitWindowingTooltipConfig =
+ TooltipEducationViewConfig(
+ tooltipViewLayout = R.layout.desktop_windowing_education_left_arrow_tooltip,
+ tooltipColorScheme = tooltipColorScheme,
+ tooltipViewGlobalCoordinates = tooltipGlobalCoordinates,
+ tooltipText =
+ getString(R.string.windowing_desktop_mode_exit_education_tooltip),
+ arrowDirection =
+ DesktopWindowingEducationTooltipController.TooltipArrowDirection.LEFT,
+ onDismissAction = {},
+ onEducationClickAction = {
+ openHandleMenuCallback(captionState.runningTaskInfo.taskId)
+ },
+ )
+ windowingEducationViewController.showEducationTooltip(
+ taskId = captionState.runningTaskInfo.taskId,
+ tooltipViewConfig = exitWindowingTooltipConfig,
+ )
}
- }
}
- }
-
- private inline fun runIfEducationFeatureEnabled(block: () -> Unit) {
- if (canEnterDesktopMode(context) && Flags.enableDesktopWindowingAppHandleEducation()) block()
- }
-
- private fun showEducation(captionState: CaptionState, tooltipColorScheme: TooltipColorScheme) {
- val appHandleBounds = (captionState as CaptionState.AppHandle).globalAppHandleBounds
- val tooltipGlobalCoordinates =
- Point(appHandleBounds.left + appHandleBounds.width() / 2, appHandleBounds.bottom)
- // TODO: b/370546801 - Differentiate between user dismissing the tooltip vs following the cue.
- // Populate information important to inflate app handle education tooltip.
- val appHandleTooltipConfig =
- TooltipEducationViewConfig(
- tooltipViewLayout = R.layout.desktop_windowing_education_top_arrow_tooltip,
- tooltipColorScheme = tooltipColorScheme,
- tooltipViewGlobalCoordinates = tooltipGlobalCoordinates,
- tooltipText = getString(R.string.windowing_app_handle_education_tooltip),
- arrowDirection = DesktopWindowingEducationTooltipController.TooltipArrowDirection.UP,
- onEducationClickAction = {
- launchWithExceptionHandling { showWindowingImageButtonTooltip(tooltipColorScheme) }
- openHandleMenuCallback(captionState.runningTaskInfo.taskId)
- },
- onDismissAction = {
- launchWithExceptionHandling { showWindowingImageButtonTooltip(tooltipColorScheme) }
- },
- )
-
- windowingEducationViewController.showEducationTooltip(
- tooltipViewConfig = appHandleTooltipConfig, taskId = captionState.runningTaskInfo.taskId)
- }
-
- /** Show tooltip that points to windowing image button in app handle menu */
- private suspend fun showWindowingImageButtonTooltip(tooltipColorScheme: TooltipColorScheme) {
- val appInfoPillHeight = getSize(R.dimen.desktop_mode_handle_menu_app_info_pill_height)
- val windowingOptionPillHeight = getSize(R.dimen.desktop_mode_handle_menu_windowing_pill_height)
- val appHandleMenuWidth =
- getSize(R.dimen.desktop_mode_handle_menu_width) +
- getSize(R.dimen.desktop_mode_handle_menu_pill_spacing_margin)
- val appHandleMenuMargins =
- getSize(R.dimen.desktop_mode_handle_menu_margin_top) +
- getSize(R.dimen.desktop_mode_handle_menu_pill_spacing_margin)
-
- windowDecorCaptionHandleRepository.captionStateFlow
- // After the first tooltip was dismissed, wait for 400 ms and see if the app handle menu
- // has been expanded.
- .timeout(APP_HANDLE_EDUCATION_TIMEOUT_MILLIS.milliseconds)
- .catchTimeoutAndLog {
- // TODO: b/341320146 - Log previous tooltip was dismissed
- }
- // Wait for few milliseconds before emitting the latest state.
- .debounce(APP_HANDLE_EDUCATION_DELAY_MILLIS)
- .filter { captionState ->
- // Filter out states when app handle is not visible or not expanded.
- captionState is CaptionState.AppHandle && captionState.isHandleMenuExpanded
- }
- // Before showing this tooltip, stop listening to further emissions to avoid accidentally
- // showing the same tooltip on future emissions.
- .take(1)
- .flowOn(backgroundDispatcher)
- .collectLatest { captionState ->
- captionState as CaptionState.AppHandle
- val appHandleBounds = captionState.globalAppHandleBounds
- val tooltipGlobalCoordinates =
- Point(
- appHandleBounds.left + appHandleBounds.width() / 2 + appHandleMenuWidth / 2,
- appHandleBounds.top +
- appHandleMenuMargins +
- appInfoPillHeight +
- windowingOptionPillHeight / 2)
- // Populate information important to inflate windowing image button education tooltip.
- val windowingImageButtonTooltipConfig =
- TooltipEducationViewConfig(
- tooltipViewLayout = R.layout.desktop_windowing_education_left_arrow_tooltip,
- tooltipColorScheme = tooltipColorScheme,
- tooltipViewGlobalCoordinates = tooltipGlobalCoordinates,
- tooltipText =
- getString(R.string.windowing_desktop_mode_image_button_education_tooltip),
- arrowDirection =
- DesktopWindowingEducationTooltipController.TooltipArrowDirection.LEFT,
- onEducationClickAction = {
- launchWithExceptionHandling { showExitWindowingTooltip(tooltipColorScheme) }
- toDesktopModeCallback(
- captionState.runningTaskInfo.taskId,
- DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON)
- },
- onDismissAction = {
- launchWithExceptionHandling { showExitWindowingTooltip(tooltipColorScheme) }
- },
- )
- windowingEducationViewController.showEducationTooltip(
- taskId = captionState.runningTaskInfo.taskId,
- tooltipViewConfig = windowingImageButtonTooltipConfig)
- }
- }
+ private fun tooltipColorScheme(captionState: CaptionState): TooltipColorScheme {
+ context.withStyledAttributes(
+ set = null,
+ attrs =
+ intArrayOf(
+ com.android.internal.R.attr.materialColorOnTertiaryFixed,
+ com.android.internal.R.attr.materialColorTertiaryFixed,
+ com.android.internal.R.attr.materialColorTertiaryFixedDim,
+ ),
+ defStyleAttr = 0,
+ defStyleRes = 0,
+ ) {
+ val onTertiaryFixed = getColor(/* index= */ 0, /* defValue= */ 0)
+ val tertiaryFixed = getColor(/* index= */ 1, /* defValue= */ 0)
+ val tertiaryFixedDim = getColor(/* index= */ 2, /* defValue= */ 0)
+ val taskInfo = (captionState as CaptionState.AppHandle).runningTaskInfo
- /** Show tooltip that points to app chip button and educates user on how to exit desktop mode */
- private suspend fun showExitWindowingTooltip(tooltipColorScheme: TooltipColorScheme) {
- windowDecorCaptionHandleRepository.captionStateFlow
- // After the previous tooltip was dismissed, wait for 400 ms and see if the user entered
- // desktop mode.
- .timeout(APP_HANDLE_EDUCATION_TIMEOUT_MILLIS.milliseconds)
- .catchTimeoutAndLog {
- // TODO: b/341320146 - Log previous tooltip was dismissed
- }
- // Wait for few milliseconds before emitting the latest state.
- .debounce(APP_HANDLE_EDUCATION_DELAY_MILLIS)
- .filter { captionState ->
- // Filter out states when app header is not visible or expanded.
- captionState is CaptionState.AppHeader && !captionState.isHeaderMenuExpanded
- }
- // Before showing this tooltip, stop listening to further emissions to avoid accidentally
- // showing the same tooltip on future emissions.
- .take(1)
- .flowOn(backgroundDispatcher)
- .collectLatest { captionState ->
- captionState as CaptionState.AppHeader
- val globalAppChipBounds = captionState.globalAppChipBounds
- val tooltipGlobalCoordinates =
- Point(
- globalAppChipBounds.right,
- globalAppChipBounds.top + globalAppChipBounds.height() / 2)
- // Populate information important to inflate exit desktop mode education tooltip.
- val exitWindowingTooltipConfig =
- TooltipEducationViewConfig(
- tooltipViewLayout = R.layout.desktop_windowing_education_left_arrow_tooltip,
- tooltipColorScheme = tooltipColorScheme,
- tooltipViewGlobalCoordinates = tooltipGlobalCoordinates,
- tooltipText = getString(R.string.windowing_desktop_mode_exit_education_tooltip),
- arrowDirection =
- DesktopWindowingEducationTooltipController.TooltipArrowDirection.LEFT,
- onDismissAction = {},
- onEducationClickAction = {
- openHandleMenuCallback(captionState.runningTaskInfo.taskId)
- },
- )
- windowingEducationViewController.showEducationTooltip(
- taskId = captionState.runningTaskInfo.taskId,
- tooltipViewConfig = exitWindowingTooltipConfig,
- )
+ val tooltipContainerColor =
+ if (decorThemeUtil.getAppTheme(taskInfo) == Theme.LIGHT) {
+ tertiaryFixed
+ } else {
+ tertiaryFixedDim
+ }
+ return TooltipColorScheme(tooltipContainerColor, onTertiaryFixed, onTertiaryFixed)
}
- }
+ return TooltipColorScheme(0, 0, 0)
+ }
- private fun tooltipColorScheme(captionState: CaptionState): TooltipColorScheme {
- context.withStyledAttributes(
- set = null,
- attrs =
- intArrayOf(
- com.android.internal.R.attr.materialColorOnTertiaryFixed,
- com.android.internal.R.attr.materialColorTertiaryFixed,
- com.android.internal.R.attr.materialColorTertiaryFixedDim),
- defStyleAttr = 0,
- defStyleRes = 0) {
- val onTertiaryFixed = getColor(/* index= */ 0, /* defValue= */ 0)
- val tertiaryFixed = getColor(/* index= */ 1, /* defValue= */ 0)
- val tertiaryFixedDim = getColor(/* index= */ 2, /* defValue= */ 0)
- val taskInfo = (captionState as CaptionState.AppHandle).runningTaskInfo
+ /**
+ * Setup callbacks for app handle education tooltips.
+ *
+ * @param openHandleMenuCallback callback invoked to open app handle menu or app chip menu.
+ * @param toDesktopModeCallback callback invoked to move task into desktop mode.
+ */
+ fun setAppHandleEducationTooltipCallbacks(
+ openHandleMenuCallback: (taskId: Int) -> Unit,
+ toDesktopModeCallback: (taskId: Int, DesktopModeTransitionSource) -> Unit,
+ ) {
+ this.openHandleMenuCallback = openHandleMenuCallback
+ this.toDesktopModeCallback = toDesktopModeCallback
+ }
- val tooltipContainerColor =
- if (decorThemeUtil.getAppTheme(taskInfo) == Theme.LIGHT) {
- tertiaryFixed
- } else {
- tertiaryFixedDim
- }
- return TooltipColorScheme(tooltipContainerColor, onTertiaryFixed, onTertiaryFixed)
+ private inline fun <T> Flow<T>.catchTimeoutAndLog(crossinline block: () -> Unit) =
+ catch { exception ->
+ if (exception is TimeoutCancellationException) block() else throw exception
}
- return TooltipColorScheme(0, 0, 0)
- }
- /**
- * Setup callbacks for app handle education tooltips.
- *
- * @param openHandleMenuCallback callback invoked to open app handle menu or app chip menu.
- * @param toDesktopModeCallback callback invoked to move task into desktop mode.
- */
- fun setAppHandleEducationTooltipCallbacks(
- openHandleMenuCallback: (taskId: Int) -> Unit,
- toDesktopModeCallback: (taskId: Int, DesktopModeTransitionSource) -> Unit
- ) {
- this.openHandleMenuCallback = openHandleMenuCallback
- this.toDesktopModeCallback = toDesktopModeCallback
- }
-
- private inline fun <T> Flow<T>.catchTimeoutAndLog(crossinline block: () -> Unit) =
- catch { exception ->
- if (exception is TimeoutCancellationException) block() else throw exception
- }
-
- private fun launchWithExceptionHandling(block: suspend () -> Unit) =
- applicationCoroutineScope.launch {
- try {
- block()
- } catch (e: Throwable) {
- Slog.e(TAG, "Error: ", e)
+ private fun launchWithExceptionHandling(block: suspend () -> Unit) =
+ applicationCoroutineScope.launch {
+ try {
+ block()
+ } catch (e: Throwable) {
+ Slog.e(TAG, "Error: ", e)
+ }
}
- }
- /**
- * Listens to the changes to [WindowingEducationProto#hasAppHandleHintViewedTimestampMillis()] in
- * datastore proto object.
- *
- * If [SHOULD_OVERRIDE_EDUCATION_CONDITIONS] is true, this flow will always emit false. That means
- * it will always emit app handle hint has not been viewed yet.
- */
- private fun isAppHandleHintViewedFlow(): Flow<Boolean> =
- appHandleEducationDatastoreRepository.dataStoreFlow
- .map { preferences ->
- preferences.hasAppHandleHintViewedTimestampMillis() && !SHOULD_OVERRIDE_EDUCATION_CONDITIONS
- }
- .distinctUntilChanged()
+ /**
+ * Listens to the changes to [WindowingEducationProto#hasAppHandleHintViewedTimestampMillis()]
+ * in datastore proto object.
+ *
+ * If [SHOULD_OVERRIDE_EDUCATION_CONDITIONS] is true, this flow will always emit false. That
+ * means it will always emit app handle hint has not been viewed yet.
+ */
+ private fun isAppHandleHintViewedFlow(): Flow<Boolean> =
+ appHandleEducationDatastoreRepository.dataStoreFlow
+ .map { preferences ->
+ preferences.hasAppHandleHintViewedTimestampMillis() &&
+ !SHOULD_OVERRIDE_EDUCATION_CONDITIONS
+ }
+ .distinctUntilChanged()
- /**
- * Listens to the changes to [WindowingEducationProto#hasAppHandleHintUsedTimestampMillis()] in
- * datastore proto object.
- */
- private suspend fun isAppHandleHintUsed(): Boolean =
- appHandleEducationDatastoreRepository.dataStoreFlow.first().hasAppHandleHintUsedTimestampMillis()
+ /**
+ * Listens to the changes to [WindowingEducationProto#hasAppHandleHintUsedTimestampMillis()] in
+ * datastore proto object.
+ */
+ private suspend fun isAppHandleHintUsed(): Boolean =
+ appHandleEducationDatastoreRepository.dataStoreFlow
+ .first()
+ .hasAppHandleHintUsedTimestampMillis()
- private fun getSize(@DimenRes resourceId: Int): Int {
- if (resourceId == Resources.ID_NULL) return 0
- return context.resources.getDimensionPixelSize(resourceId)
- }
+ private fun getSize(@DimenRes resourceId: Int): Int {
+ if (resourceId == Resources.ID_NULL) return 0
+ return context.resources.getDimensionPixelSize(resourceId)
+ }
- private fun getString(@StringRes resId: Int): String = context.resources.getString(resId)
+ private fun getString(@StringRes resId: Int): String = context.resources.getString(resId)
- companion object {
- const val TAG = "AppHandleEducationController"
- val APP_HANDLE_EDUCATION_DELAY_MILLIS: Long
- get() = SystemProperties.getLong("persist.windowing_app_handle_education_delay", 3000L)
+ companion object {
+ const val TAG = "AppHandleEducationController"
+ val APP_HANDLE_EDUCATION_DELAY_MILLIS: Long
+ get() = SystemProperties.getLong("persist.windowing_app_handle_education_delay", 3000L)
- val APP_HANDLE_EDUCATION_TIMEOUT_MILLIS: Long
- get() = SystemProperties.getLong("persist.windowing_app_handle_education_timeout", 400L)
+ val APP_HANDLE_EDUCATION_TIMEOUT_MILLIS: Long
+ get() = SystemProperties.getLong("persist.windowing_app_handle_education_timeout", 400L)
- val SHOULD_OVERRIDE_EDUCATION_CONDITIONS: Boolean
- get() =
- SystemProperties.getBoolean(
- "persist.desktop_windowing_app_handle_education_override_conditions", false)
- }
+ val SHOULD_OVERRIDE_EDUCATION_CONDITIONS: Boolean
+ get() =
+ SystemProperties.getBoolean(
+ "persist.desktop_windowing_app_handle_education_override_conditions",
+ false,
+ )
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationFilter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationFilter.kt
index 7a7829334fb6..9990846fc92e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationFilter.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationFilter.kt
@@ -32,106 +32,114 @@ import java.time.Duration
/** Filters incoming app handle education triggers based on set conditions. */
class AppHandleEducationFilter(
private val context: Context,
- private val appHandleEducationDatastoreRepository: AppHandleEducationDatastoreRepository
+ private val appHandleEducationDatastoreRepository: AppHandleEducationDatastoreRepository,
) {
- private val usageStatsManager =
- context.getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager
-
- /**
- * Returns true if conditions to show app handle education are met, returns false otherwise.
- *
- * If [SHOULD_OVERRIDE_EDUCATION_CONDITIONS] is true, this method will always return
- * ![captionState.isHandleMenuExpanded].
- */
- suspend fun shouldShowAppHandleEducation(captionState: CaptionState): Boolean {
- if ((captionState as CaptionState.AppHandle).isHandleMenuExpanded) return false
- if (SHOULD_OVERRIDE_EDUCATION_CONDITIONS) return true
-
- val focusAppPackageName =
- captionState.runningTaskInfo.topActivityInfo?.packageName ?: return false
- val windowingEducationProto = appHandleEducationDatastoreRepository.windowingEducationProto()
-
- return isFocusAppInAllowlist(focusAppPackageName) &&
- !isOtherEducationShowing() &&
- hasSufficientTimeSinceSetup() &&
- !isAppHandleHintViewedBefore(windowingEducationProto) &&
- !isAppHandleHintUsedBefore(windowingEducationProto) &&
- hasMinAppUsage(windowingEducationProto, focusAppPackageName)
- }
-
- private fun isFocusAppInAllowlist(focusAppPackageName: String): Boolean =
- focusAppPackageName in
- context.resources.getStringArray(
- R.array.desktop_windowing_app_handle_education_allowlist_apps)
-
- // TODO: b/350953004 - Add checks based on App compat
- // TODO: b/350951797 - Add checks based on PKT tips education
- private fun isOtherEducationShowing(): Boolean = isTaskbarEducationShowing()
-
- private fun isTaskbarEducationShowing(): Boolean =
- Secure.getInt(context.contentResolver, Secure.LAUNCHER_TASKBAR_EDUCATION_SHOWING, 0) == 1
-
- private fun hasSufficientTimeSinceSetup(): Boolean =
- Duration.ofMillis(SystemClock.elapsedRealtime()) >
- convertIntegerResourceToDuration(
- R.integer.desktop_windowing_education_required_time_since_setup_seconds)
-
- private fun isAppHandleHintViewedBefore(windowingEducationProto: WindowingEducationProto): Boolean =
- windowingEducationProto.hasAppHandleHintViewedTimestampMillis()
-
- private fun isAppHandleHintUsedBefore(windowingEducationProto: WindowingEducationProto): Boolean =
- windowingEducationProto.hasAppHandleHintUsedTimestampMillis()
-
- private suspend fun hasMinAppUsage(
- windowingEducationProto: WindowingEducationProto,
- focusAppPackageName: String
- ): Boolean =
- (launchCountByPackageName(windowingEducationProto)[focusAppPackageName] ?: 0) >=
- context.resources.getInteger(R.integer.desktop_windowing_education_min_app_launch_count)
-
- private suspend fun launchCountByPackageName(
- windowingEducationProto: WindowingEducationProto
- ): Map<String, Int> =
- if (isAppUsageCacheStale(windowingEducationProto)) {
- // Query and return user stats, update cache in datastore
- getAndCacheAppUsageStats()
- } else {
- // Return cached usage stats
- windowingEducationProto.appHandleEducation.appUsageStatsMap
- }
-
- private fun isAppUsageCacheStale(windowingEducationProto: WindowingEducationProto): Boolean {
- val currentTime = currentTimeInDuration()
- val lastUpdateTime =
- Duration.ofMillis(
- windowingEducationProto.appHandleEducation.appUsageStatsLastUpdateTimestampMillis)
- val appUsageStatsCachingInterval =
- convertIntegerResourceToDuration(
- R.integer.desktop_windowing_education_app_usage_cache_interval_seconds)
- return (currentTime - lastUpdateTime) > appUsageStatsCachingInterval
- }
-
- private suspend fun getAndCacheAppUsageStats(): Map<String, Int> {
- val currentTime = currentTimeInDuration()
- val appUsageStats = queryAppUsageStats()
- appHandleEducationDatastoreRepository.updateAppUsageStats(appUsageStats, currentTime)
- return appUsageStats
- }
-
- private fun queryAppUsageStats(): Map<String, Int> {
- val endTime = currentTimeInDuration()
- val appLaunchInterval =
- convertIntegerResourceToDuration(
- R.integer.desktop_windowing_education_app_launch_interval_seconds)
- val startTime = endTime - appLaunchInterval
-
- return usageStatsManager
- .queryAndAggregateUsageStats(startTime.toMillis(), endTime.toMillis())
- .mapValues { it.value.appLaunchCount }
- }
-
- private fun convertIntegerResourceToDuration(@IntegerRes resourceId: Int): Duration =
- Duration.ofSeconds(context.resources.getInteger(resourceId).toLong())
-
- private fun currentTimeInDuration(): Duration = Duration.ofMillis(System.currentTimeMillis())
+ private val usageStatsManager =
+ context.getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager
+
+ /**
+ * Returns true if conditions to show app handle education are met, returns false otherwise.
+ *
+ * If [SHOULD_OVERRIDE_EDUCATION_CONDITIONS] is true, this method will always return
+ * ![captionState.isHandleMenuExpanded].
+ */
+ suspend fun shouldShowAppHandleEducation(captionState: CaptionState): Boolean {
+ if ((captionState as CaptionState.AppHandle).isHandleMenuExpanded) return false
+ if (SHOULD_OVERRIDE_EDUCATION_CONDITIONS) return true
+
+ val focusAppPackageName =
+ captionState.runningTaskInfo.topActivityInfo?.packageName ?: return false
+ val windowingEducationProto =
+ appHandleEducationDatastoreRepository.windowingEducationProto()
+
+ return isFocusAppInAllowlist(focusAppPackageName) &&
+ !isOtherEducationShowing() &&
+ hasSufficientTimeSinceSetup() &&
+ !isAppHandleHintViewedBefore(windowingEducationProto) &&
+ !isAppHandleHintUsedBefore(windowingEducationProto) &&
+ hasMinAppUsage(windowingEducationProto, focusAppPackageName)
+ }
+
+ private fun isFocusAppInAllowlist(focusAppPackageName: String): Boolean =
+ focusAppPackageName in
+ context.resources.getStringArray(
+ R.array.desktop_windowing_app_handle_education_allowlist_apps
+ )
+
+ // TODO: b/350953004 - Add checks based on App compat
+ // TODO: b/350951797 - Add checks based on PKT tips education
+ private fun isOtherEducationShowing(): Boolean = isTaskbarEducationShowing()
+
+ private fun isTaskbarEducationShowing(): Boolean =
+ Secure.getInt(context.contentResolver, Secure.LAUNCHER_TASKBAR_EDUCATION_SHOWING, 0) == 1
+
+ private fun hasSufficientTimeSinceSetup(): Boolean =
+ Duration.ofMillis(SystemClock.elapsedRealtime()) >
+ convertIntegerResourceToDuration(
+ R.integer.desktop_windowing_education_required_time_since_setup_seconds
+ )
+
+ private fun isAppHandleHintViewedBefore(
+ windowingEducationProto: WindowingEducationProto
+ ): Boolean = windowingEducationProto.hasAppHandleHintViewedTimestampMillis()
+
+ private fun isAppHandleHintUsedBefore(
+ windowingEducationProto: WindowingEducationProto
+ ): Boolean = windowingEducationProto.hasAppHandleHintUsedTimestampMillis()
+
+ private suspend fun hasMinAppUsage(
+ windowingEducationProto: WindowingEducationProto,
+ focusAppPackageName: String,
+ ): Boolean =
+ (launchCountByPackageName(windowingEducationProto)[focusAppPackageName] ?: 0) >=
+ context.resources.getInteger(R.integer.desktop_windowing_education_min_app_launch_count)
+
+ private suspend fun launchCountByPackageName(
+ windowingEducationProto: WindowingEducationProto
+ ): Map<String, Int> =
+ if (isAppUsageCacheStale(windowingEducationProto)) {
+ // Query and return user stats, update cache in datastore
+ getAndCacheAppUsageStats()
+ } else {
+ // Return cached usage stats
+ windowingEducationProto.appHandleEducation.appUsageStatsMap
+ }
+
+ private fun isAppUsageCacheStale(windowingEducationProto: WindowingEducationProto): Boolean {
+ val currentTime = currentTimeInDuration()
+ val lastUpdateTime =
+ Duration.ofMillis(
+ windowingEducationProto.appHandleEducation.appUsageStatsLastUpdateTimestampMillis
+ )
+ val appUsageStatsCachingInterval =
+ convertIntegerResourceToDuration(
+ R.integer.desktop_windowing_education_app_usage_cache_interval_seconds
+ )
+ return (currentTime - lastUpdateTime) > appUsageStatsCachingInterval
+ }
+
+ private suspend fun getAndCacheAppUsageStats(): Map<String, Int> {
+ val currentTime = currentTimeInDuration()
+ val appUsageStats = queryAppUsageStats()
+ appHandleEducationDatastoreRepository.updateAppUsageStats(appUsageStats, currentTime)
+ return appUsageStats
+ }
+
+ private fun queryAppUsageStats(): Map<String, Int> {
+ val endTime = currentTimeInDuration()
+ val appLaunchInterval =
+ convertIntegerResourceToDuration(
+ R.integer.desktop_windowing_education_app_launch_interval_seconds
+ )
+ val startTime = endTime - appLaunchInterval
+
+ return usageStatsManager
+ .queryAndAggregateUsageStats(startTime.toMillis(), endTime.toMillis())
+ .mapValues { it.value.appLaunchCount }
+ }
+
+ private fun convertIntegerResourceToDuration(@IntegerRes resourceId: Int): Duration =
+ Duration.ofSeconds(context.resources.getInteger(resourceId).toLong())
+
+ private fun currentTimeInDuration(): Duration = Duration.ofMillis(System.currentTimeMillis())
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppToWebEducationController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppToWebEducationController.kt
index 693da81ec4a0..bfe1b12c9605 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppToWebEducationController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppToWebEducationController.kt
@@ -53,8 +53,8 @@ import kotlinx.coroutines.launch
/**
* Controls App-to-Web education end to end.
*
- * Listen to usages of App-to-Web, calls an api to check if the education
- * should be shown and controls education UI.
+ * Listen to usages of App-to-Web, calls an api to check if the education should be shown and
+ * controls education UI.
*/
@OptIn(kotlinx.coroutines.FlowPreview::class)
@kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -88,8 +88,9 @@ class AppToWebEducationController(
.debounce(APP_TO_WEB_EDUCATION_DELAY_MILLIS)
.filter { captionState ->
captionState !is CaptionState.NoCaption &&
- appToWebEducationFilter
- .shouldShowAppToWebEducation(captionState)
+ appToWebEducationFilter.shouldShowAppToWebEducation(
+ captionState
+ )
}
}
}
@@ -104,12 +105,12 @@ class AppToWebEducationController(
applicationCoroutineScope.launch {
if (isFeatureUsed()) return@launch
- windowDecorCaptionHandleRepository.appToWebUsageFlow
- .collect {
- // If user utilizes App-to-Web, mark user has used the feature
- appToWebEducationDatastoreRepository
- .updateFeatureUsedTimestampMillis(isViewed = true)
- }
+ windowDecorCaptionHandleRepository.appToWebUsageFlow.collect {
+ // If user utilizes App-to-Web, mark user has used the feature
+ appToWebEducationDatastoreRepository.updateFeatureUsedTimestampMillis(
+ isViewed = true
+ )
+ }
}
}
}
@@ -126,10 +127,8 @@ class AppToWebEducationController(
val appHandleBounds = captionState.globalAppHandleBounds
val educationWidth =
loadDimensionPixelSize(R.dimen.desktop_windowing_education_promo_width)
- educationGlobalCoordinates = Point(
- appHandleBounds.centerX() - educationWidth / 2,
- appHandleBounds.bottom
- )
+ educationGlobalCoordinates =
+ Point(appHandleBounds.centerX() - educationWidth / 2, appHandleBounds.bottom)
taskId = captionState.runningTaskInfo.taskId
}
@@ -152,19 +151,22 @@ class AppToWebEducationController(
viewGlobalCoordinates = educationGlobalCoordinates,
educationText = getString(R.string.desktop_windowing_app_to_web_education_text),
widthId = R.dimen.desktop_windowing_education_promo_width,
- heightId = R.dimen.desktop_windowing_education_promo_height
+ heightId = R.dimen.desktop_windowing_education_promo_height,
)
windowingEducationViewController.showEducation(
- viewConfig = educationConfig, taskId = taskId)
+ viewConfig = educationConfig,
+ taskId = taskId,
+ )
}
private fun educationColorScheme(captionState: CaptionState): EducationColorScheme? {
- val taskInfo: RunningTaskInfo = when (captionState) {
- is CaptionState.AppHandle -> captionState.runningTaskInfo
- is CaptionState.AppHeader -> captionState.runningTaskInfo
- else -> return null
- }
+ val taskInfo: RunningTaskInfo =
+ when (captionState) {
+ is CaptionState.AppHandle -> captionState.runningTaskInfo
+ is CaptionState.AppHeader -> captionState.runningTaskInfo
+ else -> return null
+ }
val colorScheme = decorThemeUtil.getColorScheme(taskInfo)
val tooltipContainerColor = colorScheme.surfaceBright.toArgb()
@@ -178,8 +180,7 @@ class AppToWebEducationController(
*/
private fun isEducationViewLimitReachedFlow(): Flow<Boolean> =
appToWebEducationDatastoreRepository.dataStoreFlow
- .map { preferences ->
- appToWebEducationFilter.isEducationViewLimitReached(preferences)}
+ .map { preferences -> appToWebEducationFilter.isEducationViewLimitReached(preferences) }
.distinctUntilChanged()
/**
@@ -199,9 +200,6 @@ class AppToWebEducationController(
companion object {
const val TAG = "AppToWebEducationController"
val APP_TO_WEB_EDUCATION_DELAY_MILLIS: Long
- get() = SystemProperties.getLong(
- "persist.windowing_app_handle_education_delay",
- 3000L
- )
+ get() = SystemProperties.getLong("persist.windowing_app_handle_education_delay", 3000L)
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppToWebEducationFilter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppToWebEducationFilter.kt
index feee6ed86da1..e272b54dd907 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppToWebEducationFilter.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppToWebEducationFilter.kt
@@ -30,39 +30,41 @@ import java.time.Duration
/** Filters incoming App-to-Web education triggers based on set conditions. */
class AppToWebEducationFilter(
private val context: Context,
- private val appToWebEducationDatastoreRepository: AppToWebEducationDatastoreRepository
+ private val appToWebEducationDatastoreRepository: AppToWebEducationDatastoreRepository,
) {
/** Returns true if conditions to show App-to-web education are met, returns false otherwise. */
suspend fun shouldShowAppToWebEducation(captionState: CaptionState): Boolean {
- val (taskInfo: RunningTaskInfo, isCapturedLinkAvailable: Boolean) = when (captionState) {
- is CaptionState.AppHandle ->
- Pair(captionState.runningTaskInfo, captionState.isCapturedLinkAvailable)
- is CaptionState.AppHeader ->
- Pair(captionState.runningTaskInfo, captionState.isCapturedLinkAvailable)
- else -> return false
- }
+ val (taskInfo: RunningTaskInfo, isCapturedLinkAvailable: Boolean) =
+ when (captionState) {
+ is CaptionState.AppHandle ->
+ Pair(captionState.runningTaskInfo, captionState.isCapturedLinkAvailable)
+ is CaptionState.AppHeader ->
+ Pair(captionState.runningTaskInfo, captionState.isCapturedLinkAvailable)
+ else -> return false
+ }
val focusAppPackageName = taskInfo.topActivityInfo?.packageName ?: return false
val windowingEducationProto = appToWebEducationDatastoreRepository.windowingEducationProto()
return !isOtherEducationShowing() &&
- !isEducationViewLimitReached(windowingEducationProto) &&
- hasSufficientTimeSinceSetup() &&
- !isFeatureUsedBefore(windowingEducationProto) &&
- isCapturedLinkAvailable &&
- isFocusAppInAllowlist(focusAppPackageName)
+ !isEducationViewLimitReached(windowingEducationProto) &&
+ hasSufficientTimeSinceSetup() &&
+ !isFeatureUsedBefore(windowingEducationProto) &&
+ isCapturedLinkAvailable &&
+ isFocusAppInAllowlist(focusAppPackageName)
}
private fun isFocusAppInAllowlist(focusAppPackageName: String): Boolean =
focusAppPackageName in
- context.resources.getStringArray(
- R.array.desktop_windowing_app_to_web_education_allowlist_apps)
+ context.resources.getStringArray(
+ R.array.desktop_windowing_app_to_web_education_allowlist_apps
+ )
// TODO: b/350953004 - Add checks based on App compat
// TODO: b/350951797 - Add checks based on PKT tips education
- private fun isOtherEducationShowing(): Boolean = isTaskbarEducationShowing() ||
- isCompatUiEducationShowing()
+ private fun isOtherEducationShowing(): Boolean =
+ isTaskbarEducationShowing() || isCompatUiEducationShowing()
private fun isTaskbarEducationShowing(): Boolean =
Secure.getInt(context.contentResolver, Secure.LAUNCHER_TASKBAR_EDUCATION_SHOWING, 0) == 1
@@ -72,13 +74,14 @@ class AppToWebEducationFilter(
private fun hasSufficientTimeSinceSetup(): Boolean =
Duration.ofMillis(SystemClock.elapsedRealtime()) >
- convertIntegerResourceToDuration(
- R.integer.desktop_windowing_education_required_time_since_setup_seconds)
+ convertIntegerResourceToDuration(
+ R.integer.desktop_windowing_education_required_time_since_setup_seconds
+ )
/** Returns true if education is viewed maximum amount of times it should be shown. */
fun isEducationViewLimitReached(windowingEducationProto: WindowingEducationProto): Boolean =
windowingEducationProto.getAppToWebEducation().getEducationShownCount() >=
- MAXIMUM_TIMES_EDUCATION_SHOWN
+ MAXIMUM_TIMES_EDUCATION_SHOWN
private fun isFeatureUsedBefore(windowingEducationProto: WindowingEducationProto): Boolean =
windowingEducationProto.hasFeatureUsedTimestampMillis()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppHandleEducationDatastoreRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppHandleEducationDatastoreRepository.kt
index 5e0c0007e2eb..3e120b09a0b6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppHandleEducationDatastoreRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppHandleEducationDatastoreRepository.kt
@@ -42,103 +42,109 @@ import kotlinx.coroutines.flow.first
class AppHandleEducationDatastoreRepository
@VisibleForTesting
constructor(private val dataStore: DataStore<WindowingEducationProto>) {
- constructor(
- context: Context
- ) : this(
- DataStoreFactory.create(
- serializer = WindowingEducationProtoSerializer,
- produceFile = { context.dataStoreFile(APP_HANDLE_EDUCATION_DATASTORE_FILEPATH) }))
+ constructor(
+ context: Context
+ ) : this(
+ DataStoreFactory.create(
+ serializer = WindowingEducationProtoSerializer,
+ produceFile = { context.dataStoreFile(APP_HANDLE_EDUCATION_DATASTORE_FILEPATH) },
+ )
+ )
- /** Provides dataStore.data flow and handles exceptions thrown during collection */
- val dataStoreFlow: Flow<WindowingEducationProto> =
- dataStore.data.catch { exception ->
- // dataStore.data throws an IOException when an error is encountered when reading data
- if (exception is IOException) {
- Log.e(
- TAG,
- "Error in reading app handle education related data from datastore, data is " +
- "stored in a file named $APP_HANDLE_EDUCATION_DATASTORE_FILEPATH",
- exception)
- } else {
- throw exception
+ /** Provides dataStore.data flow and handles exceptions thrown during collection */
+ val dataStoreFlow: Flow<WindowingEducationProto> =
+ dataStore.data.catch { exception ->
+ // dataStore.data throws an IOException when an error is encountered when reading data
+ if (exception is IOException) {
+ Log.e(
+ TAG,
+ "Error in reading app handle education related data from datastore, data is " +
+ "stored in a file named $APP_HANDLE_EDUCATION_DATASTORE_FILEPATH",
+ exception,
+ )
+ } else {
+ throw exception
+ }
}
- }
- /**
- * Reads and returns the [WindowingEducationProto] Proto object from the DataStore. If the
- * DataStore is empty or there's an error reading, it returns the default value of Proto.
- */
- suspend fun windowingEducationProto(): WindowingEducationProto = dataStoreFlow.first()
+ /**
+ * Reads and returns the [WindowingEducationProto] Proto object from the DataStore. If the
+ * DataStore is empty or there's an error reading, it returns the default value of Proto.
+ */
+ suspend fun windowingEducationProto(): WindowingEducationProto = dataStoreFlow.first()
- /**
- * Updates [WindowingEducationProto.appHandleHintViewedTimestampMillis_] field
- * in datastore with current timestamp if [isViewed] is true, if not then
- * clears the field.
- */
- suspend fun updateAppHandleHintViewedTimestampMillis(isViewed: Boolean) {
- dataStore.updateData { preferences ->
- if (isViewed) {
- preferences
- .toBuilder()
- .setAppHandleHintViewedTimestampMillis(System.currentTimeMillis())
- .build()
- } else {
- preferences.toBuilder().clearAppHandleHintViewedTimestampMillis().build()
- }
+ /**
+ * Updates [WindowingEducationProto.appHandleHintViewedTimestampMillis_] field in datastore with
+ * current timestamp if [isViewed] is true, if not then clears the field.
+ */
+ suspend fun updateAppHandleHintViewedTimestampMillis(isViewed: Boolean) {
+ dataStore.updateData { preferences ->
+ if (isViewed) {
+ preferences
+ .toBuilder()
+ .setAppHandleHintViewedTimestampMillis(System.currentTimeMillis())
+ .build()
+ } else {
+ preferences.toBuilder().clearAppHandleHintViewedTimestampMillis().build()
+ }
+ }
}
- }
- /**
- * Updates [WindowingEducationProto.appHandleHintUsedTimestampMillis_] field
- * in datastore with current timestamp if [isViewed] is true, if not then
- * clears the field.
- */
- suspend fun updateAppHandleHintUsedTimestampMillis(isViewed: Boolean) {
- dataStore.updateData { preferences ->
- if (isViewed) {
- preferences.toBuilder().setAppHandleHintUsedTimestampMillis(System.currentTimeMillis()).build()
- } else {
- preferences.toBuilder().clearAppHandleHintUsedTimestampMillis().build()
- }
+ /**
+ * Updates [WindowingEducationProto.appHandleHintUsedTimestampMillis_] field in datastore with
+ * current timestamp if [isViewed] is true, if not then clears the field.
+ */
+ suspend fun updateAppHandleHintUsedTimestampMillis(isViewed: Boolean) {
+ dataStore.updateData { preferences ->
+ if (isViewed) {
+ preferences
+ .toBuilder()
+ .setAppHandleHintUsedTimestampMillis(System.currentTimeMillis())
+ .build()
+ } else {
+ preferences.toBuilder().clearAppHandleHintUsedTimestampMillis().build()
+ }
+ }
}
- }
- /**
- * Updates [AppHandleEducation.appUsageStats] and
- * [AppHandleEducation.appUsageStatsLastUpdateTimestampMillis] fields in datastore with
- * [appUsageStats] and [appUsageStatsLastUpdateTimestamp].
- */
- suspend fun updateAppUsageStats(
- appUsageStats: Map<String, Int>,
- appUsageStatsLastUpdateTimestamp: Duration
- ) {
- val currentAppHandleProto = windowingEducationProto().appHandleEducation.toBuilder()
- currentAppHandleProto
- .putAllAppUsageStats(appUsageStats)
- .setAppUsageStatsLastUpdateTimestampMillis(appUsageStatsLastUpdateTimestamp.toMillis())
- dataStore.updateData { preferences: WindowingEducationProto ->
- preferences.toBuilder().setAppHandleEducation(currentAppHandleProto).build()
+ /**
+ * Updates [AppHandleEducation.appUsageStats] and
+ * [AppHandleEducation.appUsageStatsLastUpdateTimestampMillis] fields in datastore with
+ * [appUsageStats] and [appUsageStatsLastUpdateTimestamp].
+ */
+ suspend fun updateAppUsageStats(
+ appUsageStats: Map<String, Int>,
+ appUsageStatsLastUpdateTimestamp: Duration,
+ ) {
+ val currentAppHandleProto = windowingEducationProto().appHandleEducation.toBuilder()
+ currentAppHandleProto
+ .putAllAppUsageStats(appUsageStats)
+ .setAppUsageStatsLastUpdateTimestampMillis(appUsageStatsLastUpdateTimestamp.toMillis())
+ dataStore.updateData { preferences: WindowingEducationProto ->
+ preferences.toBuilder().setAppHandleEducation(currentAppHandleProto).build()
+ }
}
- }
- companion object {
- private const val TAG = "AppHandleEducationDatastoreRepository"
- private const val APP_HANDLE_EDUCATION_DATASTORE_FILEPATH = "app_handle_education.pb"
+ companion object {
+ private const val TAG = "AppHandleEducationDatastoreRepository"
+ private const val APP_HANDLE_EDUCATION_DATASTORE_FILEPATH = "app_handle_education.pb"
- object WindowingEducationProtoSerializer : Serializer<WindowingEducationProto> {
+ object WindowingEducationProtoSerializer : Serializer<WindowingEducationProto> {
- override val defaultValue: WindowingEducationProto =
- WindowingEducationProto.getDefaultInstance()
+ override val defaultValue: WindowingEducationProto =
+ WindowingEducationProto.getDefaultInstance()
- override suspend fun readFrom(input: InputStream): WindowingEducationProto =
- try {
- WindowingEducationProto.parseFrom(input)
- } catch (exception: InvalidProtocolBufferException) {
- throw CorruptionException("Cannot read proto.", exception)
- }
+ override suspend fun readFrom(input: InputStream): WindowingEducationProto =
+ try {
+ WindowingEducationProto.parseFrom(input)
+ } catch (exception: InvalidProtocolBufferException) {
+ throw CorruptionException("Cannot read proto.", exception)
+ }
- override suspend fun writeTo(windowingProto: WindowingEducationProto, output: OutputStream) =
- windowingProto.writeTo(output)
+ override suspend fun writeTo(
+ windowingProto: WindowingEducationProto,
+ output: OutputStream,
+ ) = windowingProto.writeTo(output)
+ }
}
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppToWebEducationDatastoreRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppToWebEducationDatastoreRepository.kt
index 8be6e6dff6fe..e5ad901d1435 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppToWebEducationDatastoreRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppToWebEducationDatastoreRepository.kt
@@ -25,12 +25,12 @@ import androidx.datastore.core.Serializer
import androidx.datastore.dataStoreFile
import com.android.framework.protobuf.InvalidProtocolBufferException
import com.android.internal.annotations.VisibleForTesting
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.catch
-import kotlinx.coroutines.flow.first
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.catch
+import kotlinx.coroutines.flow.first
/** Updates data in App-to-Web's education datastore. */
class AppToWebEducationDatastoreRepository
@@ -41,7 +41,9 @@ constructor(private val dataStore: DataStore<WindowingEducationProto>) {
) : this(
DataStoreFactory.create(
serializer = WindowingEducationProtoSerializer,
- produceFile = { context.dataStoreFile(APP_TO_WEB_EDUCATION_DATASTORE_FILEPATH) }))
+ produceFile = { context.dataStoreFile(APP_TO_WEB_EDUCATION_DATASTORE_FILEPATH) },
+ )
+ )
/** Provides dataStore.data flow and handles exceptions thrown during collection */
val dataStoreFlow: Flow<WindowingEducationProto> =
@@ -51,8 +53,10 @@ constructor(private val dataStore: DataStore<WindowingEducationProto>) {
Slog.e(
TAG,
"Error in reading App-to-Web education related data from datastore," +
- "data is stored in a file named" +
- "$APP_TO_WEB_EDUCATION_DATASTORE_FILEPATH", exception)
+ "data is stored in a file named" +
+ "$APP_TO_WEB_EDUCATION_DATASTORE_FILEPATH",
+ exception,
+ )
} else {
throw exception
}
@@ -72,26 +76,26 @@ constructor(private val dataStore: DataStore<WindowingEducationProto>) {
dataStore.updateData { preferences ->
if (isViewed) {
preferences
- .toBuilder().setFeatureUsedTimestampMillis(System.currentTimeMillis()).build()
+ .toBuilder()
+ .setFeatureUsedTimestampMillis(System.currentTimeMillis())
+ .build()
} else {
preferences.toBuilder().clearFeatureUsedTimestampMillis().build()
}
}
}
- /**
- * Increases [AppToWebEducation.educationShownCount] field by one.
- */
+ /** Increases [AppToWebEducation.educationShownCount] field by one. */
suspend fun updateEducationShownCount() {
val currentAppHandleProto = windowingEducationProto().appToWebEducation.toBuilder()
- currentAppHandleProto
- .setEducationShownCount(currentAppHandleProto.getEducationShownCount() + 1)
+ currentAppHandleProto.setEducationShownCount(
+ currentAppHandleProto.getEducationShownCount() + 1
+ )
dataStore.updateData { preferences ->
preferences.toBuilder().setAppToWebEducation(currentAppHandleProto).build()
}
}
-
companion object {
private const val TAG = "AppToWebEducationDatastoreRepository"
private const val APP_TO_WEB_EDUCATION_DATASTORE_FILEPATH = "app_to_web_education.pb"
@@ -110,7 +114,7 @@ constructor(private val dataStore: DataStore<WindowingEducationProto>) {
override suspend fun writeTo(
windowingProto: WindowingEducationProto,
- output: OutputStream
+ output: OutputStream,
) = windowingProto.writeTo(output)
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
index e4f83333edbf..4c316de98744 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
@@ -64,6 +64,7 @@ import com.android.wm.shell.shared.annotations.ExternalThread;
import com.android.wm.shell.sysui.KeyguardChangeListener;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.FocusTransitionObserver;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.transition.Transitions.TransitionFinishCallback;
@@ -89,6 +90,7 @@ public class KeyguardTransitionHandler
private final ArrayMap<IBinder, StartedTransition> mStartedTransitions = new ArrayMap<>();
private final TaskStackListenerImpl mTaskStackListener;
+ private final FocusTransitionObserver mFocusTransitionObserver;
/**
* Local IRemoteTransition implementations registered by the keyguard service.
@@ -129,7 +131,8 @@ public class KeyguardTransitionHandler
@NonNull Transitions transitions,
@NonNull TaskStackListenerImpl taskStackListener,
@NonNull Handler mainHandler,
- @NonNull ShellExecutor mainExecutor) {
+ @NonNull ShellExecutor mainExecutor,
+ @NonNull FocusTransitionObserver focusTransitionObserver) {
mTransitions = transitions;
mShellController = shellController;
mDisplayController = displayController;
@@ -137,6 +140,7 @@ public class KeyguardTransitionHandler
mMainExecutor = mainExecutor;
mTaskStackListener = taskStackListener;
shellInit.addInitCallback(this::onInit, this);
+ mFocusTransitionObserver = focusTransitionObserver;
}
private void onInit() {
@@ -396,7 +400,8 @@ public class KeyguardTransitionHandler
final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
if (taskInfo != null && taskInfo.taskId != INVALID_TASK_ID
&& taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
- && taskInfo.isFocused && change.getContainer() != null) {
+ && mFocusTransitionObserver.hasGlobalFocus(taskInfo)
+ && change.getContainer() != null) {
wct.setWindowingMode(change.getContainer(), WINDOWING_MODE_FULLSCREEN);
wct.setBounds(change.getContainer(), null);
return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 30f1948efa2d..fb4afe41e193 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -26,6 +26,8 @@ import static android.util.RotationUtils.rotateBounds;
import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_PIP;
import static com.android.wm.shell.ShellTaskOrganizer.taskListenerTypeToString;
+import static com.android.wm.shell.desktopmode.DesktopModeUtils.calculateInitialBounds;
+import static com.android.wm.shell.desktopmode.DesktopTasksController.DESKTOP_MODE_INITIAL_BOUNDS_SCALE;
import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA;
import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_BOUNDS;
import static com.android.wm.shell.pip.PipAnimationController.FRACTION_START;
@@ -152,6 +154,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
@Nullable private final PipPerfHintController mPipPerfHintController;
private final Optional<DesktopRepository> mDesktopRepositoryOptional;
private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
+ private final DisplayController mDisplayController;
protected final ShellTaskOrganizer mTaskOrganizer;
protected final ShellExecutor mMainExecutor;
@@ -425,6 +428,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
mPipPerfHintController = pipPerfHintControllerOptional.orElse(null);
mDesktopRepositoryOptional = desktopRepositoryOptional;
mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
+ mDisplayController = displayController;
mTaskOrganizer = shellTaskOrganizer;
mMainExecutor = mainExecutor;
@@ -754,19 +758,30 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
/** Returns the bounds to restore to when exiting PIP mode. */
// TODO(b/377581840): Instead of manually tracking bounds, use bounds from Core.
public Rect getExitDestinationBounds() {
- if (isPipLaunchedInDesktopMode()) {
- final Rect freeformBounds = mDesktopRepositoryOptional.get().removeBoundsBeforeMinimize(
+ if (isPipExitingToDesktopMode()) {
+ // If we are exiting PiP while device is in Desktop mode:
+ // 1) If PiP was entered via Desktop minimize (e.g. via minimize button), restore to the
+ // previous freeform bounds that is saved in DesktopRepository.
+ // 2) If PiP was entered through other means (e.g. user swipe up), exit to initial
+ // freeform bounds. Note that this case has a flicker at the moment (b/379984108).
+ Rect freeformBounds = mDesktopRepositoryOptional.get().removeBoundsBeforeMinimize(
mTaskInfo.taskId);
- return Objects.requireNonNullElseGet(freeformBounds, mPipBoundsState::getDisplayBounds);
+ return freeformBounds != null
+ ? freeformBounds
+ : calculateInitialBounds(
+ mDisplayController.getDisplayLayout(mTaskInfo.displayId),
+ mTaskInfo,
+ DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
}
return mPipBoundsState.getDisplayBounds();
}
- /** Returns whether PiP was launched while in desktop mode. */
- // TODO(377581840): Update this check to include non-minimized cases, e.g. split to PiP etc.
- private boolean isPipLaunchedInDesktopMode() {
+ /** Returns whether PiP is exiting while we're in desktop mode. */
+ // TODO(b/377581840): Update this check to include non-minimized cases, e.g. split to PiP etc.
+ private boolean isPipExitingToDesktopMode() {
return Flags.enableDesktopWindowingPip() && mDesktopRepositoryOptional.isPresent()
- && mDesktopRepositoryOptional.get().isMinimizedTask(mTaskInfo.taskId);
+ && (mDesktopRepositoryOptional.get().getVisibleTaskCount(mTaskInfo.displayId) > 0
+ || isDisplayInFreeform());
}
private void exitLaunchIntoPipTask(WindowContainerTransaction wct) {
@@ -1827,23 +1842,28 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
== SPLIT_POSITION_TOP_OR_LEFT;
}
+ private boolean isDisplayInFreeform() {
+ final DisplayAreaInfo tdaInfo = mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(
+ mTaskInfo.displayId);
+ if (tdaInfo != null) {
+ return tdaInfo.configuration.windowConfiguration.getWindowingMode()
+ == WINDOWING_MODE_FREEFORM;
+ }
+ return false;
+ }
+
/**
* The windowing mode to restore to when resizing out of PIP direction. Defaults to undefined
* and can be overridden to restore to an alternate windowing mode.
*/
public int getOutPipWindowingMode() {
- final DisplayAreaInfo tdaInfo = mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(
- mTaskInfo.displayId);
-
- // If PiP was launched while in desktop mode (we should return the task to freeform
- // windowing mode):
+ // If we are exiting PiP while the device is in Desktop mode (the task should expand to
+ // freeform windowing mode):
// 1) If the display windowing mode is freeform, set windowing mode to undefined so it will
// resolve the windowing mode to the display's windowing mode.
// 2) If the display windowing mode is not freeform, set windowing mode to freeform.
- if (tdaInfo != null && isPipLaunchedInDesktopMode()) {
- final int displayWindowingMode =
- tdaInfo.configuration.windowConfiguration.getWindowingMode();
- if (displayWindowingMode == WINDOWING_MODE_FREEFORM) {
+ if (isPipExitingToDesktopMode()) {
+ if (isDisplayInFreeform()) {
return WINDOWING_MODE_UNDEFINED;
} else {
return WINDOWING_MODE_FREEFORM;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
index 6d2df952ee58..2c5d346224a3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
@@ -254,6 +254,7 @@ public class PipController implements ConfigurationChangeListener,
@Override
public void onConfigurationChanged(Configuration newConfiguration) {
mPipDisplayLayoutState.onConfigurationChanged();
+ mPipTouchHandler.onConfigurationChanged();
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
index 65972fb7df48..44cc563eadf4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
@@ -363,12 +363,10 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha
mMotionHelper.synchronizePinnedStackBounds();
reloadResources();
- /*
- if (mPipTaskOrganizer.isInPip()) {
+ if (mPipTransitionState.isInPip()) {
// Recreate the dismiss target for the new orientation.
mPipDismissTargetHandler.createOrUpdateDismissTarget();
}
- */
}
void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index 1ba29a238474..8f02c1b157b5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -35,6 +35,7 @@ import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.PictureInPictureParams;
+import android.app.TaskInfo;
import android.content.Context;
import android.graphics.Point;
import android.graphics.PointF;
@@ -57,6 +58,7 @@ import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.PipMenuController;
import com.android.wm.shell.common.pip.PipUtils;
+import com.android.wm.shell.common.split.SplitScreenUtils;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip2.animation.PipAlphaAnimator;
import com.android.wm.shell.pip2.animation.PipEnterAnimator;
@@ -74,8 +76,8 @@ public class PipTransition extends PipTransitionController implements
private static final String TAG = PipTransition.class.getSimpleName();
// Used when for ENTERING_PIP state update.
- private static final String PIP_TASK_TOKEN = "pip_task_token";
private static final String PIP_TASK_LEASH = "pip_task_leash";
+ private static final String PIP_TASK_INFO = "pip_task_info";
// Used for PiP CHANGING_BOUNDS state update.
static final String PIP_START_TX = "pip_start_tx";
@@ -245,8 +247,8 @@ public class PipTransition extends PipTransitionController implements
// Update the PipTransitionState while supplying the PiP leash and token to be cached.
Bundle extra = new Bundle();
- extra.putParcelable(PIP_TASK_TOKEN, pipChange.getContainer());
extra.putParcelable(PIP_TASK_LEASH, pipChange.getLeash());
+ extra.putParcelable(PIP_TASK_INFO, pipChange.getTaskInfo());
mPipTransitionState.setState(PipTransitionState.ENTERING_PIP, extra);
if (isInSwipePipToHomeTransition()) {
@@ -899,10 +901,10 @@ public class PipTransition extends PipTransitionController implements
Preconditions.checkState(extra != null,
"No extra bundle for " + mPipTransitionState);
- mPipTransitionState.setPipTaskToken(extra.getParcelable(
- PIP_TASK_TOKEN, WindowContainerToken.class));
mPipTransitionState.setPinnedTaskLeash(extra.getParcelable(
PIP_TASK_LEASH, SurfaceControl.class));
+ mPipTransitionState.setPipTaskInfo(extra.getParcelable(
+ PIP_TASK_INFO, TaskInfo.class));
boolean hasValidTokenAndLeash = mPipTransitionState.getPipTaskToken() != null
&& mPipTransitionState.getPinnedTaskLeash() != null;
@@ -910,16 +912,21 @@ public class PipTransition extends PipTransitionController implements
"Unexpected bundle for " + mPipTransitionState);
break;
case PipTransitionState.EXITED_PIP:
- mPipTransitionState.setPipTaskToken(null);
+ // Save the PiP bounds in case, we re-enter the PiP with the same component.
+ float snapFraction = mPipBoundsAlgorithm.getSnapFraction(
+ mPipBoundsState.getBounds());
+ mPipBoundsState.saveReentryState(snapFraction);
+
mPipTransitionState.setPinnedTaskLeash(null);
+ mPipTransitionState.setPipTaskInfo(null);
break;
}
}
@Override
public boolean isPackageActiveInPip(@Nullable String packageName) {
- return packageName != null
- && mPipBoundsState.getLastPipComponentName() != null
- && packageName.equals(mPipBoundsState.getLastPipComponentName().getPackageName());
+ final TaskInfo inPipTask = mPipTransitionState.getPipTaskInfo();
+ return packageName != null && inPipTask != null && mPipTransitionState.isInPip()
+ && packageName.equals(SplitScreenUtils.getPackageName(inPipTask.baseIntent));
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
index 8e90bfee2636..6f9f40a487c7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
@@ -17,6 +17,7 @@
package com.android.wm.shell.pip2.phone;
import android.annotation.IntDef;
+import android.app.TaskInfo;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Handler;
@@ -133,17 +134,17 @@ public class PipTransitionState {
private final Rect mSwipePipToHomeAppBounds = new Rect();
//
- // Tokens and leashes
+ // Task related caches
//
- // pinned PiP task's WC token
- @Nullable
- private WindowContainerToken mPipTaskToken;
-
// pinned PiP task's leash
@Nullable
private SurfaceControl mPinnedTaskLeash;
+ // pinned PiP task info
+ @Nullable
+ private TaskInfo mPipTaskInfo;
+
// Overlay leash potentially used during swipe PiP to home transition;
// if null while mInSwipePipToHomeTransition is true, then srcRectHint was invalid.
@Nullable
@@ -305,11 +306,7 @@ public class PipTransitionState {
}
@Nullable WindowContainerToken getPipTaskToken() {
- return mPipTaskToken;
- }
-
- public void setPipTaskToken(@Nullable WindowContainerToken token) {
- mPipTaskToken = token;
+ return mPipTaskInfo != null ? mPipTaskInfo.getToken() : null;
}
@Nullable SurfaceControl getPinnedTaskLeash() {
@@ -320,6 +317,14 @@ public class PipTransitionState {
mPinnedTaskLeash = leash;
}
+ @Nullable TaskInfo getPipTaskInfo() {
+ return mPipTaskInfo;
+ }
+
+ void setPipTaskInfo(@Nullable TaskInfo pipTaskInfo) {
+ mPipTaskInfo = pipTaskInfo;
+ }
+
/**
* @return true if either in swipe or button-nav fixed rotation.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 4f0f6760a951..6e0e696e15fe 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -582,6 +582,7 @@ public class SplitScreenController implements SplitDragPolicy.Starter,
@Nullable WindowContainerToken hideTaskToken) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
"Legacy startTask does not support hide task token");
+ if (isTaskInSplitScreenForeground(taskId)) return;
final int[] result = new int[1];
IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() {
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHandleManageWindowsMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHandleManageWindowsMenu.kt
index b8c91519422c..4d95cde1492f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHandleManageWindowsMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHandleManageWindowsMenu.kt
@@ -24,7 +24,7 @@ import android.view.WindowManager
import android.window.TaskSnapshot
import androidx.compose.ui.graphics.toArgb
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
-import com.android.wm.shell.shared.desktopmode.ManageWindowsViewContainer
+import com.android.wm.shell.shared.multiinstance.ManageWindowsViewContainer
import com.android.wm.shell.shared.split.SplitScreenConstants
import com.android.wm.shell.splitscreen.SplitScreenController
import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenu.kt
index dd68105d28c1..101467df03ac 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopHeaderManageWindowsMenu.kt
@@ -33,7 +33,7 @@ import com.android.window.flags.Flags
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.desktopmode.DesktopRepository
-import com.android.wm.shell.shared.desktopmode.ManageWindowsViewContainer
+import com.android.wm.shell.shared.multiinstance.ManageWindowsViewContainer
import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer
import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewContainer
import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewHostViewContainer
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 4497e35e82f1..04ef7c1dc461 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -39,7 +39,7 @@ import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.Indica
import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR;
import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
-import static com.android.wm.shell.shared.desktopmode.ManageWindowsViewContainer.MANAGE_WINDOWS_MINIMUM_INSTANCES;
+import static com.android.wm.shell.shared.multiinstance.ManageWindowsViewContainer.MANAGE_WINDOWS_MINIMUM_INSTANCES;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
@@ -107,6 +107,7 @@ import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.compatui.CompatUIController;
import com.android.wm.shell.desktopmode.DesktopActivityOrientationChangeHandler;
+import com.android.wm.shell.desktopmode.DesktopImmersiveController;
import com.android.wm.shell.desktopmode.DesktopModeEventLogger;
import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger;
import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger.DesktopUiEventEnum;
@@ -174,6 +175,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
private final DisplayController mDisplayController;
private final SyncTransactionQueue mSyncQueue;
private final DesktopTasksController mDesktopTasksController;
+ private final DesktopImmersiveController mDesktopImmersiveController;
private final InputManager mInputManager;
private final InteractionJankMonitor mInteractionJankMonitor;
private final MultiInstanceHelper mMultiInstanceHelper;
@@ -249,6 +251,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
SyncTransactionQueue syncQueue,
Transitions transitions,
Optional<DesktopTasksController> desktopTasksController,
+ DesktopImmersiveController desktopImmersiveController,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
InteractionJankMonitor interactionJankMonitor,
AppToWebGenericLinksParser genericLinksParser,
@@ -279,6 +282,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
syncQueue,
transitions,
desktopTasksController,
+ desktopImmersiveController,
genericLinksParser,
assistContentRequester,
multiInstanceHelper,
@@ -318,6 +322,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
SyncTransactionQueue syncQueue,
Transitions transitions,
Optional<DesktopTasksController> desktopTasksController,
+ DesktopImmersiveController desktopImmersiveController,
AppToWebGenericLinksParser genericLinksParser,
AssistContentRequester assistContentRequester,
MultiInstanceHelper multiInstanceHelper,
@@ -351,6 +356,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
mSyncQueue = syncQueue;
mTransitions = transitions;
mDesktopTasksController = desktopTasksController.get();
+ mDesktopImmersiveController = desktopImmersiveController;
mMultiInstanceHelper = multiInstanceHelper;
mShellCommandHandler = shellCommandHandler;
mWindowManager = windowManager;
@@ -601,16 +607,33 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
if (decoration == null) {
return;
}
- mDesktopTasksController.toggleDesktopTaskFullImmersiveState(decoration.mTaskInfo);
+ if (mDesktopRepository.isTaskInFullImmersiveState(taskId)) {
+ mDesktopModeUiEventLogger.log(decoration.mTaskInfo,
+ DesktopUiEventEnum.DESKTOP_WINDOW_MAXIMIZE_BUTTON_MENU_TAP_TO_RESTORE);
+ mDesktopImmersiveController.moveTaskToNonImmersive(
+ decoration.mTaskInfo, DesktopImmersiveController.ExitReason.USER_INTERACTION);
+ } else {
+ mDesktopModeUiEventLogger.log(decoration.mTaskInfo,
+ DesktopUiEventEnum.DESKTOP_WINDOW_MAXIMIZE_BUTTON_MENU_TAP_TO_IMMERSIVE);
+ mDesktopImmersiveController.moveTaskToImmersive(decoration.mTaskInfo);
+ }
decoration.closeMaximizeMenu();
}
- public void onSnapResize(int taskId, boolean left, InputMethod inputMethod) {
+ /** Snap-resize a task to the left or right side of the desktop. */
+ public void onSnapResize(int taskId, boolean left, InputMethod inputMethod, boolean fromMenu) {
final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
if (decoration == null) {
return;
}
+ if (fromMenu) {
+ final DesktopModeUiEventLogger.DesktopUiEventEnum event = left
+ ? DesktopUiEventEnum.DESKTOP_WINDOW_MAXIMIZE_BUTTON_MENU_TAP_TO_TILE_TO_LEFT
+ : DesktopUiEventEnum.DESKTOP_WINDOW_MAXIMIZE_BUTTON_MENU_TAP_TO_TILE_TO_RIGHT;
+ mDesktopModeUiEventLogger.log(decoration.mTaskInfo, event);
+ }
+
mInteractionJankMonitor.begin(decoration.mTaskSurface, mContext, mMainHandler,
Cuj.CUJ_DESKTOP_MODE_SNAP_RESIZE, "maximize_menu_resizable");
mDesktopTasksController.handleInstantSnapResizingTask(
@@ -939,6 +962,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
decoration.closeMaximizeMenu();
} else {
mHasLongClicked = true;
+ mDesktopModeUiEventLogger.log(decoration.mTaskInfo,
+ DesktopUiEventEnum.DESKTOP_WINDOW_MAXIMIZE_BUTTON_REVEAL_MENU);
decoration.createMaximizeMenu();
}
return true;
@@ -1582,8 +1607,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
final DesktopModeTouchEventListener touchEventListener =
new DesktopModeTouchEventListener(taskInfo, taskPositioner);
- InputMethod inputMethod = DesktopModeEventLogger.getInputMethodFromMotionEvent(
- touchEventListener.mMotionEvent);
windowDecoration.setOnMaximizeOrRestoreClickListener(() -> {
onMaximizeOrRestore(taskInfo.taskId, "maximize_menu", ResizeTrigger.MAXIMIZE_MENU,
touchEventListener.mMotionEvent);
@@ -1594,11 +1617,15 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
return Unit.INSTANCE;
});
windowDecoration.setOnLeftSnapClickListener(() -> {
- onSnapResize(taskInfo.taskId, /* isLeft= */ true, inputMethod);
+ onSnapResize(taskInfo.taskId, /* isLeft= */ true,
+ DesktopModeEventLogger.getInputMethodFromMotionEvent(
+ touchEventListener.mMotionEvent), /* fromMenu= */ true);
return Unit.INSTANCE;
});
windowDecoration.setOnRightSnapClickListener(() -> {
- onSnapResize(taskInfo.taskId, /* isLeft= */ false, inputMethod);
+ onSnapResize(taskInfo.taskId, /* isLeft= */ false,
+ DesktopModeEventLogger.getInputMethodFromMotionEvent(
+ touchEventListener.mMotionEvent), /* fromMenu= */ true);
return Unit.INSTANCE;
});
windowDecoration.setOnToDesktopClickListener(desktopModeTransitionSource -> {
@@ -1627,6 +1654,14 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
CompatUIController.launchUserAspectRatioSettings(mContext, taskInfo);
return Unit.INSTANCE;
});
+ windowDecoration.setOnMaximizeHoverListener(() -> {
+ if (!windowDecoration.isMaximizeMenuActive()) {
+ mDesktopModeUiEventLogger.log(taskInfo,
+ DesktopUiEventEnum.DESKTOP_WINDOW_MAXIMIZE_BUTTON_REVEAL_MENU);
+ windowDecoration.createMaximizeMenu();
+ }
+ return Unit.INSTANCE;
+ });
windowDecoration.setCaptionListeners(
touchEventListener, touchEventListener, touchEventListener, touchEventListener);
windowDecoration.setExclusionRegionListener(mExclusionRegionListener);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index d57044a4d27c..96cc559a64ae 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -104,7 +104,7 @@ import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository;
import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
-import com.android.wm.shell.shared.desktopmode.ManageWindowsViewContainer;
+import com.android.wm.shell.shared.multiinstance.ManageWindowsViewContainer;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
import com.android.wm.shell.windowdecor.viewholder.AppHandleViewHolder;
@@ -155,6 +155,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
private Function0<Unit> mOnNewWindowClickListener;
private Function0<Unit> mOnManageWindowsClickListener;
private Function0<Unit> mOnChangeAspectRatioClickListener;
+ private Function0<Unit> mOnMaximizeHoverListener;
private DragPositioningCallback mDragPositioningCallback;
private DragResizeInputListener mDragResizeListener;
private Runnable mCurrentViewHostRunnable = null;
@@ -370,6 +371,11 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
mOnChangeAspectRatioClickListener = listener;
}
+ /** Registers a listener to be called when the maximize header button is hovered. */
+ void setOnMaximizeHoverListener(Function0<Unit> listener) {
+ mOnMaximizeHoverListener = listener;
+ }
+
void setCaptionListeners(
View.OnClickListener onCaptionButtonClickListener,
View.OnTouchListener onCaptionTouchListener,
@@ -841,12 +847,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
mOnCaptionGenericMotionListener,
mAppName,
mAppIconBitmap,
- () -> {
- if (!isMaximizeMenuActive()) {
- createMaximizeMenu();
- }
- return Unit.INSTANCE;
- });
+ mOnMaximizeHoverListener);
}
throw new IllegalArgumentException("Unexpected layout resource id");
}
@@ -1458,10 +1459,13 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
@NonNull Function1<Integer, Unit> onIconClickListener
) {
if (mTaskInfo.isFreeform()) {
+ // The menu uses display-wide coordinates for positioning, so make position the sum
+ // of task position and caption position.
+ final Rect taskBounds = mTaskInfo.configuration.windowConfiguration.getBounds();
mManageWindowsMenu = new DesktopHeaderManageWindowsMenu(
mTaskInfo,
- /* x= */ mResult.mCaptionX,
- /* y= */ mResult.mCaptionY + mResult.mCaptionTopPadding,
+ /* x= */ taskBounds.left + mResult.mCaptionX,
+ /* y= */ taskBounds.top + mResult.mCaptionY + mResult.mCaptionTopPadding,
mDisplayController,
mRootTaskDisplayAreaOrganizer,
mContext,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java
index 6f72d34eb0ae..c8aff78cbb36 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java
@@ -74,8 +74,7 @@ public final class DragResizeWindowGeometry {
mFineTaskCorners = new TaskCorners(mTaskSize, fineCornerSize, disabledEdge);
// Save touch areas for each edge.
- mTaskEdges = new TaskEdges(mTaskSize, mResizeHandleEdgeOutset, mResizeHandleEdgeInset,
- mDisabledEdge);
+ mTaskEdges = new TaskEdges(mTaskSize, mResizeHandleEdgeOutset, mDisabledEdge);
}
/**
@@ -459,7 +458,7 @@ public final class DragResizeWindowGeometry {
private final @NonNull DisabledEdge mDisabledEdge;
private TaskEdges(@NonNull Size taskSize, int resizeHandleThickness,
- int resizeHandleEdgeInset, DisabledEdge disabledEdge) {
+ DisabledEdge disabledEdge) {
// Save touch areas for each edge.
mDisabledEdge = disabledEdge;
// Save touch areas for each edge.
@@ -471,16 +470,16 @@ public final class DragResizeWindowGeometry {
mLeftEdgeBounds = new Rect(
-resizeHandleThickness,
0,
- resizeHandleEdgeInset,
+ resizeHandleThickness,
taskSize.getHeight());
mRightEdgeBounds = new Rect(
- taskSize.getWidth() - resizeHandleEdgeInset,
+ taskSize.getWidth() - resizeHandleThickness,
0,
taskSize.getWidth() + resizeHandleThickness,
taskSize.getHeight());
mBottomEdgeBounds = new Rect(
-resizeHandleThickness,
- taskSize.getHeight() - resizeHandleEdgeInset,
+ taskSize.getHeight() - resizeHandleThickness,
taskSize.getWidth() + resizeHandleThickness,
taskSize.getHeight() + resizeHandleThickness);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/transition/TransitionStateHolderTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/transition/TransitionStateHolderTest.kt
new file mode 100644
index 000000000000..7b1d27a8b823
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/transition/TransitionStateHolderTest.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common.transition
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.server.testutils.any
+import com.android.wm.shell.TestShellExecutor
+import com.android.wm.shell.recents.RecentsTransitionHandler
+import com.android.wm.shell.recents.RecentsTransitionStateListener
+import com.android.wm.shell.recents.RecentsTransitionStateListener.TRANSITION_STATE_ANIMATING
+import com.android.wm.shell.recents.RecentsTransitionStateListener.TRANSITION_STATE_NOT_RUNNING
+import com.android.wm.shell.recents.RecentsTransitionStateListener.TRANSITION_STATE_REQUESTED
+import com.android.wm.shell.sysui.ShellInit
+import kotlin.test.assertNotNull
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import org.mockito.kotlin.never
+
+/**
+ * Test class for {@link TransitionStateHolder}
+ *
+ * Usage: atest WMShellUnitTests:TransitionStateHolderTest
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class TransitionStateHolderTest {
+
+ lateinit var recentTransitionHandler: RecentsTransitionHandler
+ lateinit var shellInit: ShellInit
+
+ @Before
+ fun before() {
+ recentTransitionHandler = mock(RecentsTransitionHandler::class.java)
+ shellInit = ShellInit(TestShellExecutor())
+ }
+
+ @Test
+ fun `No TransitionStateHolder listeners before initialization`() {
+ TransitionStateHolder(shellInit, recentTransitionHandler)
+ verify(recentTransitionHandler, never()).addTransitionStateListener(any())
+ }
+
+ @Test
+ fun `When TransitionStateHolder initialized a listener has been registered `() {
+ TransitionStateHolder(shellInit, recentTransitionHandler)
+ shellInit.init()
+ assertNotNull(recentsTransitionStateListener)
+ }
+
+ @Test
+ fun `When TransitionStateHolder is created no recent animation running`() {
+ val holder = TransitionStateHolder(shellInit, recentTransitionHandler)
+ shellInit.init()
+ assertFalse(holder.isRecentsTransitionRunning())
+ }
+
+ @Test
+ fun `Recent animation running updates after callback value`() {
+ val holder = TransitionStateHolder(shellInit, recentTransitionHandler)
+ shellInit.init()
+
+ assertFalse(holder.isRecentsTransitionRunning())
+
+ recentsTransitionStateListener.onTransitionStateChanged(TRANSITION_STATE_NOT_RUNNING)
+ assertFalse(holder.isRecentsTransitionRunning())
+
+ recentsTransitionStateListener.onTransitionStateChanged(TRANSITION_STATE_REQUESTED)
+ assertTrue(holder.isRecentsTransitionRunning())
+
+ recentsTransitionStateListener.onTransitionStateChanged(TRANSITION_STATE_ANIMATING)
+ assertTrue(holder.isRecentsTransitionRunning())
+ }
+
+ private val recentsTransitionStateListener: RecentsTransitionStateListener
+ get() = ArgumentCaptor.forClass(RecentsTransitionStateListener::class.java).run {
+ verify(recentTransitionHandler).addTransitionStateListener(capture())
+ value
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxSurfaceBuilderTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxSurfaceBuilderTest.kt
new file mode 100644
index 000000000000..68d9bf9b926f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxSurfaceBuilderTest.kt
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui.letterbox
+
+import android.content.Context
+import android.testing.AndroidTestingRunner
+import android.view.SurfaceControl
+import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn
+import com.android.wm.shell.ShellTestCase
+import java.util.function.Consumer
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
+import org.mockito.Mockito.verify
+import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.never
+import org.mockito.kotlin.times
+import org.mockito.verification.VerificationMode
+
+/**
+ * Tests for [LetterboxSurfaceBuilder].
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:LetterboxSurfaceBuilderTest
+ */
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class LetterboxSurfaceBuilderTest : ShellTestCase() {
+
+ @Test
+ fun `When surface is created mandatory methods are invoked`() {
+ runTestScenario { r ->
+ r.invokeBuilder()
+
+ r.checkNameIsSet(expected = true)
+ r.checkCallSiteIsSet(expected = true)
+ r.checkSurfaceIsHidden(invoked = true, isHidden = true)
+ r.checkColorLayerIsSet(expected = true)
+ r.checkParentLeashIsSet(expected = true)
+ r.checkSetLayerIsInvoked(expected = true)
+ r.checkColorSpaceAgnosticIsSet(expected = true, value = true)
+ r.checkColorIsSetFromLetterboxConfiguration(expected = true)
+ }
+ }
+
+ /**
+ * Runs a test scenario providing a Robot.
+ */
+ fun runTestScenario(consumer: Consumer<LetterboxSurfaceBuilderRobotTest>) {
+ val robot = LetterboxSurfaceBuilderRobotTest(mContext)
+ consumer.accept(robot)
+ }
+
+ class LetterboxSurfaceBuilderRobotTest(val ctx: Context) {
+
+ private val letterboxConfiguration: LetterboxConfiguration
+ private val letterboxSurfaceBuilder: LetterboxSurfaceBuilder
+ private val tx: SurfaceControl.Transaction
+ private val parentLeash: SurfaceControl
+ private val surfaceBuilder: SurfaceControl.Builder
+
+ companion object {
+ @JvmStatic
+ val TEST_SURFACE_NAME = "SurfaceForTest"
+
+ @JvmStatic
+ val TEST_SURFACE_CALL_SITE = "CallSiteForTest"
+ }
+
+ init {
+ letterboxConfiguration = LetterboxConfiguration(ctx)
+ letterboxSurfaceBuilder = LetterboxSurfaceBuilder(letterboxConfiguration)
+ tx = org.mockito.kotlin.mock<SurfaceControl.Transaction>()
+ doReturn(tx).`when`(tx).setLayer(anyOrNull(), anyOrNull())
+ doReturn(tx).`when`(tx).setColorSpaceAgnostic(anyOrNull(), anyOrNull())
+ parentLeash = org.mockito.kotlin.mock<SurfaceControl>()
+ surfaceBuilder = SurfaceControl.Builder()
+ spyOn(surfaceBuilder)
+ }
+
+ fun invokeBuilder() {
+ letterboxSurfaceBuilder.createSurface(
+ tx,
+ parentLeash,
+ TEST_SURFACE_NAME,
+ TEST_SURFACE_CALL_SITE,
+ surfaceBuilder
+ )
+ }
+
+ fun checkNameIsSet(expected: Boolean) {
+ verify(surfaceBuilder, expected.asMode())
+ .setName(TEST_SURFACE_NAME)
+ }
+
+ fun checkCallSiteIsSet(expected: Boolean) {
+ verify(surfaceBuilder, expected.asMode())
+ .setCallsite(TEST_SURFACE_CALL_SITE)
+ }
+
+ fun checkSurfaceIsHidden(invoked: Boolean, isHidden: Boolean) {
+ verify(surfaceBuilder, invoked.asMode())
+ .setHidden(isHidden)
+ }
+
+ fun checkColorLayerIsSet(expected: Boolean) {
+ verify(surfaceBuilder, expected.asMode()).setColorLayer()
+ }
+
+ fun checkParentLeashIsSet(expected: Boolean) {
+ verify(surfaceBuilder, expected.asMode()).setParent(parentLeash)
+ }
+
+ fun checkSetLayerIsInvoked(expected: Boolean) {
+ verify(tx, expected.asMode()).setLayer(anyOrNull(), ArgumentMatchers.anyInt())
+ }
+
+ fun checkColorSpaceAgnosticIsSet(expected: Boolean, value: Boolean) {
+ verify(tx, expected.asMode()).setColorSpaceAgnostic(anyOrNull(), eq(value))
+ }
+
+ fun checkColorIsSetFromLetterboxConfiguration(expected: Boolean) {
+ val components = letterboxConfiguration.getBackgroundColorRgbArray()
+ verify(tx, expected.asMode()).setColor(anyOrNull(), eq(components))
+ }
+
+ private fun Boolean.asMode(): VerificationMode = if (this) times(1) else never()
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserverTest.kt
index 1ae1c3fc4563..9c6afcb8be63 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserverTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxTransitionObserverTest.kt
@@ -16,10 +16,13 @@
package com.android.wm.shell.compatui.letterbox
+import android.graphics.Point
+import android.graphics.Rect
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
import android.testing.AndroidTestingRunner
+import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_CLOSE
import androidx.test.filters.SmallTest
import com.android.window.flags.Flags
@@ -33,12 +36,12 @@ import java.util.function.Consumer
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mockito
import org.mockito.kotlin.any
import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.times
-import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.verify
import org.mockito.verification.VerificationMode
/**
@@ -93,7 +96,7 @@ class LetterboxTransitionObserverTest : ShellTestCase() {
r.creationEventDetected(expected = false)
r.visibilityEventDetected(expected = false)
r.destroyEventDetected(expected = false)
- r.boundsEventDetected(expected = false)
+ r.updateSurfaceBoundsEventDetected(expected = false)
}
}
}
@@ -107,14 +110,23 @@ class LetterboxTransitionObserverTest : ShellTestCase() {
inputBuilder {
buildTransitionInfo()
- r.createTopActivityChange(inputBuilder = this, isLetterboxed = true)
+ r.createTopActivityChange(
+ inputBuilder = this,
+ isLetterboxed = true,
+ taskPosition = Point(20, 30),
+ taskWidth = 200,
+ taskHeight = 300
+ )
}
validateOutput {
r.creationEventDetected(expected = true)
r.visibilityEventDetected(expected = true, visible = true)
r.destroyEventDetected(expected = false)
- r.boundsEventDetected(expected = true)
+ r.updateSurfaceBoundsEventDetected(
+ expected = true,
+ taskBounds = Rect(20, 30, 200, 300)
+ )
}
}
}
@@ -135,7 +147,7 @@ class LetterboxTransitionObserverTest : ShellTestCase() {
r.creationEventDetected(expected = false)
r.visibilityEventDetected(expected = true, visible = false)
r.destroyEventDetected(expected = false)
- r.boundsEventDetected(expected = false)
+ r.updateSurfaceBoundsEventDetected(expected = false)
}
}
}
@@ -156,7 +168,7 @@ class LetterboxTransitionObserverTest : ShellTestCase() {
r.destroyEventDetected(expected = true)
r.creationEventDetected(expected = false)
r.visibilityEventDetected(expected = false, visible = false)
- r.boundsEventDetected(expected = false)
+ r.updateSurfaceBoundsEventDetected(expected = false)
}
}
}
@@ -189,10 +201,10 @@ class LetterboxTransitionObserverTest : ShellTestCase() {
val observerFactory: () -> LetterboxTransitionObserver
init {
- executor = Mockito.mock(ShellExecutor::class.java)
+ executor = mock<ShellExecutor>()
shellInit = ShellInit(executor)
- transitions = Mockito.mock(Transitions::class.java)
- letterboxController = Mockito.mock(LetterboxController::class.java)
+ transitions = mock<Transitions>()
+ letterboxController = mock<LetterboxController>()
letterboxObserver =
LetterboxTransitionObserver(shellInit, transitions, letterboxController)
observerFactory = { letterboxObserver }
@@ -203,67 +215,78 @@ class LetterboxTransitionObserverTest : ShellTestCase() {
fun observer() = letterboxObserver
fun checkObservableIsRegistered(expected: Boolean) {
- Mockito.verify(transitions, expected.asMode()).registerObserver(observer())
+ verify(transitions, expected.asMode()).registerObserver(observer())
}
fun creationEventDetected(
expected: Boolean,
displayId: Int = DISPLAY_ID,
taskId: Int = TASK_ID
- ) {
- Mockito.verify(letterboxController, expected.asMode()).createLetterboxSurface(
- toLetterboxKeyMatcher(displayId, taskId),
- anyOrNull(),
- anyOrNull()
- )
- }
+ ) = verify(
+ letterboxController,
+ expected.asMode()
+ ).createLetterboxSurface(
+ eq(LetterboxKey(displayId, taskId)),
+ any<SurfaceControl.Transaction>(),
+ any<SurfaceControl>()
+ )
fun visibilityEventDetected(
expected: Boolean,
+ visible: Boolean = true,
displayId: Int = DISPLAY_ID,
- taskId: Int = TASK_ID,
- visible: Boolean? = null
- ) {
- Mockito.verify(letterboxController, expected.asMode()).updateLetterboxSurfaceVisibility(
- toLetterboxKeyMatcher(displayId, taskId),
- anyOrNull(),
- visible.asMatcher()
- )
- }
+ taskId: Int = TASK_ID
+ ) = verify(letterboxController, expected.asMode()).updateLetterboxSurfaceVisibility(
+ eq(LetterboxKey(displayId, taskId)),
+ any<SurfaceControl.Transaction>(),
+ eq(visible)
+ )
fun destroyEventDetected(
expected: Boolean,
displayId: Int = DISPLAY_ID,
taskId: Int = TASK_ID
- ) {
- Mockito.verify(letterboxController, expected.asMode()).destroyLetterboxSurface(
- toLetterboxKeyMatcher(displayId, taskId),
- anyOrNull()
- )
- }
-
- fun boundsEventDetected(
+ ) = verify(
+ letterboxController,
+ expected.asMode()
+ ).destroyLetterboxSurface(
+ eq(LetterboxKey(displayId, taskId)),
+ any<SurfaceControl.Transaction>()
+ )
+
+ fun updateSurfaceBoundsEventDetected(
expected: Boolean,
displayId: Int = DISPLAY_ID,
- taskId: Int = TASK_ID
- ) {
- Mockito.verify(letterboxController, expected.asMode()).updateLetterboxSurfaceBounds(
- toLetterboxKeyMatcher(displayId, taskId),
- anyOrNull(),
- anyOrNull()
- )
- }
+ taskId: Int = TASK_ID,
+ taskBounds: Rect = Rect()
+ ) = verify(
+ letterboxController,
+ expected.asMode()
+ ).updateLetterboxSurfaceBounds(
+ eq(LetterboxKey(displayId, taskId)),
+ any<SurfaceControl.Transaction>(),
+ eq(taskBounds)
+ )
fun createTopActivityChange(
inputBuilder: TransitionObserverInputBuilder,
isLetterboxed: Boolean = true,
displayId: Int = DISPLAY_ID,
- taskId: Int = TASK_ID
+ taskId: Int = TASK_ID,
+ taskPosition: Point = Point(),
+ taskWidth: Int = 0,
+ taskHeight: Int = 0
) {
- inputBuilder.addChange(changeTaskInfo = inputBuilder.createTaskInfo().apply {
- appCompatTaskInfo.isTopActivityLetterboxed = isLetterboxed
- this.taskId = taskId
- this.displayId = displayId
+ inputBuilder.addChange(inputBuilder.createChange(
+ changeTaskInfo = inputBuilder.createTaskInfo().apply {
+ appCompatTaskInfo.isTopActivityLetterboxed = isLetterboxed
+ this.taskId = taskId
+ this.displayId = displayId
+ }
+ ).apply {
+ endRelOffset.x = taskPosition.x
+ endRelOffset.y = taskPosition.y
+ endAbsBounds.set(Rect(0, 0, taskWidth, taskHeight))
})
}
@@ -279,16 +302,5 @@ class LetterboxTransitionObserverTest : ShellTestCase() {
}
private fun Boolean.asMode(): VerificationMode = if (this) times(1) else never()
-
- private fun Boolean?.asMatcher(): Boolean =
- if (this != null) eq(this) else any()
-
- private fun toLetterboxKeyMatcher(displayId: Int, taskId: Int): LetterboxKey {
- if (displayId < 0 || taskId < 0) {
- return any()
- } else {
- return eq(LetterboxKey(displayId, taskId))
- }
- }
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt
index eb81d59c7a5f..d4682c1325f2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt
@@ -134,6 +134,12 @@ class DesktopModeKeyGestureHandlerTest : ShellTestCase() {
null
}.whenever(inputManager).registerKeyGestureEventHandler(any())
shellInit.init()
+
+ desktopModeKeyGestureHandler = DesktopModeKeyGestureHandler(
+ context,
+ Optional.of(desktopModeWindowDecorViewModel), Optional.of(desktopTasksController),
+ inputManager, shellTaskOrganizer, focusTransitionObserver, testExecutor
+ )
}
@After
@@ -151,11 +157,6 @@ class DesktopModeKeyGestureHandlerTest : ShellTestCase() {
FLAG_USE_KEY_GESTURE_EVENT_HANDLER
)
fun keyGestureMoveToNextDisplay_shouldMoveToNextDisplay() {
- desktopModeKeyGestureHandler = DesktopModeKeyGestureHandler(
- context,
- Optional.of(desktopModeWindowDecorViewModel), Optional.of(desktopTasksController),
- inputManager, shellTaskOrganizer, focusTransitionObserver
- )
// Set up two display ids
whenever(rootTaskDisplayAreaOrganizer.displayIds)
.thenReturn(intArrayOf(DEFAULT_DISPLAY, SECOND_DISPLAY))
@@ -187,11 +188,6 @@ class DesktopModeKeyGestureHandlerTest : ShellTestCase() {
FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS
)
fun keyGestureSnapLeft_shouldSnapResizeTaskToLeft() {
- desktopModeKeyGestureHandler = DesktopModeKeyGestureHandler(
- context,
- Optional.of(desktopModeWindowDecorViewModel), Optional.of(desktopTasksController),
- inputManager, shellTaskOrganizer, focusTransitionObserver
- )
val task = setUpFreeformTask()
task.isFocused = true
whenever(shellTaskOrganizer.getRunningTasks()).thenReturn(arrayListOf(task))
@@ -206,7 +202,10 @@ class DesktopModeKeyGestureHandlerTest : ShellTestCase() {
assertThat(result).isTrue()
verify(desktopModeWindowDecorViewModel).onSnapResize(
- task.taskId, true, DesktopModeEventLogger.Companion.InputMethod.KEYBOARD
+ task.taskId,
+ true,
+ DesktopModeEventLogger.Companion.InputMethod.KEYBOARD,
+ /* fromMenu= */ false
)
}
@@ -216,11 +215,6 @@ class DesktopModeKeyGestureHandlerTest : ShellTestCase() {
FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS
)
fun keyGestureSnapRight_shouldSnapResizeTaskToRight() {
- desktopModeKeyGestureHandler = DesktopModeKeyGestureHandler(
- context,
- Optional.of(desktopModeWindowDecorViewModel), Optional.of(desktopTasksController),
- inputManager, shellTaskOrganizer, focusTransitionObserver
- )
val task = setUpFreeformTask()
task.isFocused = true
whenever(shellTaskOrganizer.getRunningTasks()).thenReturn(arrayListOf(task))
@@ -235,7 +229,10 @@ class DesktopModeKeyGestureHandlerTest : ShellTestCase() {
assertThat(result).isTrue()
verify(desktopModeWindowDecorViewModel).onSnapResize(
- task.taskId, false, DesktopModeEventLogger.Companion.InputMethod.KEYBOARD
+ task.taskId,
+ false,
+ DesktopModeEventLogger.Companion.InputMethod.KEYBOARD,
+ /* fromMenu= */ false
)
}
@@ -245,11 +242,6 @@ class DesktopModeKeyGestureHandlerTest : ShellTestCase() {
FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS
)
fun keyGestureToggleFreeformWindowSize_shouldToggleTaskSize() {
- desktopModeKeyGestureHandler = DesktopModeKeyGestureHandler(
- context,
- Optional.of(desktopModeWindowDecorViewModel), Optional.of(desktopTasksController),
- inputManager, shellTaskOrganizer, focusTransitionObserver
- )
val task = setUpFreeformTask()
task.isFocused = true
whenever(shellTaskOrganizer.getRunningTasks()).thenReturn(arrayListOf(task))
@@ -276,11 +268,6 @@ class DesktopModeKeyGestureHandlerTest : ShellTestCase() {
FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS
)
fun keyGestureMinimizeFreeformWindow_shouldMinimizeTask() {
- desktopModeKeyGestureHandler = DesktopModeKeyGestureHandler(
- context,
- Optional.of(desktopModeWindowDecorViewModel), Optional.of(desktopTasksController),
- inputManager, shellTaskOrganizer, focusTransitionObserver
- )
val task = setUpFreeformTask()
task.isFocused = true
whenever(shellTaskOrganizer.getRunningTasks()).thenReturn(arrayListOf(task))
@@ -294,7 +281,6 @@ class DesktopModeKeyGestureHandlerTest : ShellTestCase() {
val result = keyGestureEventHandler.handleKeyGestureEvent(event, null)
assertThat(result).isTrue()
- verify(desktopTasksController).minimizeTask(task)
}
private fun setUpFreeformTask(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index fc31d08b4571..5c0027220ec9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -3751,26 +3751,6 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
- fun toggleImmersive_enter_movesToImmersive() {
- val task = setUpFreeformTask(DEFAULT_DISPLAY)
- taskRepository.setTaskInFullImmersiveState(DEFAULT_DISPLAY, task.taskId, false /* immersive */)
-
- controller.toggleDesktopTaskFullImmersiveState(task)
-
- verify(mMockDesktopImmersiveController).moveTaskToImmersive(task)
- }
-
- @Test
- fun toggleImmersive_exit_movesToNonImmersive() {
- val task = setUpFreeformTask(DEFAULT_DISPLAY)
- taskRepository.setTaskInFullImmersiveState(DEFAULT_DISPLAY, task.taskId, true /* immersive */)
-
- controller.toggleDesktopTaskFullImmersiveState(task)
-
- verify(mMockDesktopImmersiveController).moveTaskToNonImmersive(eq(task), any())
- }
-
- @Test
@EnableFlags(FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
fun onTaskInfoChanged_inImmersiveUnrequestsImmersive_exits() {
val task = setUpFreeformTask(DEFAULT_DISPLAY)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/util/TransitionObserverTestUtils.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/util/TransitionObserverTestUtils.kt
index 3e26ee0deed0..0e15668a05a7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/util/TransitionObserverTestUtils.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/util/TransitionObserverTestUtils.kt
@@ -36,7 +36,6 @@ import android.window.TransitionInfo.TransitionMode
import android.window.WindowContainerToken
import com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn
import com.android.wm.shell.transition.Transitions.TransitionObserver
-import org.mockito.Mockito
import org.mockito.kotlin.mock
@DslMarker
@@ -93,10 +92,10 @@ class TransitionObserverTestContext : TransitionObserverTestStep {
*/
class TransitionObserverInputBuilder : TransitionObserverTestStep {
- private val transition = Mockito.mock(IBinder::class.java)
+ private val transition = mock<IBinder>()
private var transitionInfo: TransitionInfo? = null
- private val startTransaction = Mockito.mock(Transaction::class.java)
- private val finishTransaction = Mockito.mock(Transaction::class.java)
+ private val startTransaction = mock<Transaction>()
+ private val finishTransaction = mock<Transaction>()
fun buildTransitionInfo(
@TransitionType type: Int = TRANSIT_NONE,
@@ -143,7 +142,7 @@ class TransitionObserverInputBuilder : TransitionObserverTestStep {
taskId = id
displayId = DEFAULT_DISPLAY
configuration.windowConfiguration.windowingMode = windowingMode
- token = WindowContainerToken(Mockito.mock(IWindowContainerToken::class.java))
+ token = WindowContainerToken(mock<IWindowContainerToken>())
baseIntent = Intent().apply {
component = ComponentName("package", "component.name")
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index 83dbb7ccbd4e..153be07bd204 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -59,6 +59,7 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
import com.android.window.flags.Flags
import com.android.wm.shell.R
+import com.android.wm.shell.desktopmode.DesktopImmersiveController
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod
import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger
import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition
@@ -1002,7 +1003,7 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest
@Test
@EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
- fun testMaximizeButtonClick_requestingImmersive_togglesDesktopImmersiveState() {
+ fun testImmersiveButtonClick_entersImmersiveMode() {
val onClickListenerCaptor = forClass(View.OnClickListener::class.java)
as ArgumentCaptor<View.OnClickListener>
val decor = createOpenTaskDecoration(
@@ -1012,11 +1013,35 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest
)
val view = mock(View::class.java)
whenever(view.id).thenReturn(R.id.maximize_window)
+ whenever(mockDesktopRepository.isTaskInFullImmersiveState(decor.mTaskInfo.taskId))
+ .thenReturn(false)
onClickListenerCaptor.value.onClick(view)
- verify(mockDesktopTasksController)
- .toggleDesktopTaskFullImmersiveState(decor.mTaskInfo)
+ verify(mockDesktopImmersiveController).moveTaskToImmersive(decor.mTaskInfo)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+ fun testImmersiveRestoreButtonClick_exitsImmersiveMode() {
+ val onClickListenerCaptor = forClass(View.OnClickListener::class.java)
+ as ArgumentCaptor<View.OnClickListener>
+ val decor = createOpenTaskDecoration(
+ windowingMode = WINDOWING_MODE_FREEFORM,
+ onCaptionButtonClickListener = onClickListenerCaptor,
+ requestingImmersive = true,
+ )
+ val view = mock(View::class.java)
+ whenever(view.id).thenReturn(R.id.maximize_window)
+ whenever(mockDesktopRepository.isTaskInFullImmersiveState(decor.mTaskInfo.taskId))
+ .thenReturn(true)
+
+ onClickListenerCaptor.value.onClick(view)
+
+ verify(mockDesktopImmersiveController).moveTaskToNonImmersive(
+ decor.mTaskInfo,
+ DesktopImmersiveController.ExitReason.USER_INTERACTION
+ )
}
@Test
@@ -1046,18 +1071,19 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest
@Test
@EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
- fun testImmersiveClick_togglesImmersiveState() {
+ fun testImmersiveMenuOptionClick_entersImmersiveMode() {
val onImmersiveClickCaptor = argumentCaptor<() -> Unit>()
val decor = createOpenTaskDecoration(
windowingMode = WINDOWING_MODE_FREEFORM,
onImmersiveOrRestoreListenerCaptor = onImmersiveClickCaptor,
requestingImmersive = true,
)
+ whenever(mockDesktopRepository.isTaskInFullImmersiveState(decor.mTaskInfo.taskId))
+ .thenReturn(false)
onImmersiveClickCaptor.firstValue()
- verify(mockDesktopTasksController)
- .toggleDesktopTaskFullImmersiveState(decor.mTaskInfo)
+ verify(mockDesktopImmersiveController).moveTaskToImmersive(decor.mTaskInfo)
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
index 1670f2a6815b..080f496593cf 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
@@ -54,6 +54,7 @@ import com.android.wm.shell.common.DisplayLayout
import com.android.wm.shell.common.MultiInstanceHelper
import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.desktopmode.DesktopActivityOrientationChangeHandler
+import com.android.wm.shell.desktopmode.DesktopImmersiveController
import com.android.wm.shell.desktopmode.DesktopModeEventLogger
import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger
import com.android.wm.shell.desktopmode.DesktopRepository
@@ -112,6 +113,7 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() {
protected val displayInsetsController = mock<DisplayInsetsController>()
protected val mockSyncQueue = mock<SyncTransactionQueue>()
protected val mockDesktopTasksController = mock<DesktopTasksController>()
+ protected val mockDesktopImmersiveController = mock<DesktopImmersiveController>()
protected val mockInputMonitor = mock<InputMonitor>()
protected val mockTransitions = mock<Transitions>()
internal val mockInputMonitorFactory =
@@ -183,6 +185,7 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() {
mockSyncQueue,
mockTransitions,
Optional.of(mockDesktopTasksController),
+ mockDesktopImmersiveController,
mockGenericLinksParser,
mockAssistContentRequester,
mockMultiInstanceHelper,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java
index e7d328e28297..479f1567ed31 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java
@@ -63,6 +63,7 @@ public class DragResizeWindowGeometryTests extends ShellTestCase {
private static final int EDGE_RESIZE_HANDLE_INSET = 4;
private static final int FINE_CORNER_SIZE = EDGE_RESIZE_THICKNESS * 2 + 10;
private static final int LARGE_CORNER_SIZE = FINE_CORNER_SIZE + 10;
+ private static final int SMALL_OFFSET = 10;
private static final DragResizeWindowGeometry GEOMETRY = new DragResizeWindowGeometry(
TASK_CORNER_RADIUS, TASK_SIZE, EDGE_RESIZE_THICKNESS, EDGE_RESIZE_HANDLE_INSET,
FINE_CORNER_SIZE, LARGE_CORNER_SIZE, DragResizeWindowGeometry.DisabledEdge.NONE);
@@ -147,15 +148,19 @@ public class DragResizeWindowGeometryTests extends ShellTestCase {
assertThat(region.contains(point.x + EDGE_RESIZE_THICKNESS, point.y)).isTrue();
assertThat(region.contains(point.x - EDGE_RESIZE_THICKNESS, point.y)).isTrue();
// Vertically along the edge is not contained.
- assertThat(region.contains(point.x, point.y - EDGE_RESIZE_THICKNESS)).isFalse();
- assertThat(region.contains(point.x, point.y + EDGE_RESIZE_THICKNESS + 10)).isFalse();
+ assertThat(
+ region.contains(point.x, point.y - EDGE_RESIZE_THICKNESS - SMALL_OFFSET)).isFalse();
+ assertThat(
+ region.contains(point.x, point.y + EDGE_RESIZE_THICKNESS + SMALL_OFFSET)).isFalse();
}
private static void verifyVerticalEdge(@NonNull Region region, @NonNull Point point) {
assertThat(region.contains(point.x, point.y)).isTrue();
// Horizontally along the edge is not contained.
- assertThat(region.contains(point.x + EDGE_RESIZE_THICKNESS, point.y)).isFalse();
- assertThat(region.contains(point.x - EDGE_RESIZE_THICKNESS, point.y)).isFalse();
+ assertThat(
+ region.contains(point.x + EDGE_RESIZE_THICKNESS + SMALL_OFFSET, point.y)).isFalse();
+ assertThat(
+ region.contains(point.x - EDGE_RESIZE_THICKNESS - SMALL_OFFSET, point.y)).isFalse();
// Vertically along the edge is contained.
assertThat(region.contains(point.x, point.y - EDGE_RESIZE_THICKNESS)).isTrue();
assertThat(region.contains(point.x, point.y + EDGE_RESIZE_THICKNESS)).isTrue();
diff --git a/libs/hwui/jni/android_graphics_RenderNode.cpp b/libs/hwui/jni/android_graphics_RenderNode.cpp
index d9dc8eb8c1b1..a0291a9e806f 100644
--- a/libs/hwui/jni/android_graphics_RenderNode.cpp
+++ b/libs/hwui/jni/android_graphics_RenderNode.cpp
@@ -594,14 +594,12 @@ static void android_view_RenderNode_requestPositionUpdates(JNIEnv* env, jobject,
Matrix4 transform;
SkIRect clipBounds;
uirenderer::Rect initialClipBounds;
- const auto clipFlags = props.getClippingFlags();
if (enableClip) {
- if (clipFlags) {
- props.getClippingRectForFlags(clipFlags, &initialClipBounds);
- } else {
- // Works for RenderNode::damageSelf()
- initialClipBounds.set(DIRTY_MIN, DIRTY_MIN, DIRTY_MAX, DIRTY_MAX);
- }
+ // SurfaceView never draws beyond its bounds regardless of if it can or not,
+ // so if clip-to-bounds is disabled just use the bounds as the starting point
+ // regardless
+ const auto clipFlags = props.getClippingFlags();
+ props.getClippingRectForFlags(clipFlags | CLIP_TO_BOUNDS, &initialClipBounds);
clipBounds =
info.damageAccumulator
->computeClipAndTransform(initialClipBounds.toSkRect(), &transform)
diff --git a/media/java/android/media/AudioPlaybackConfiguration.java b/media/java/android/media/AudioPlaybackConfiguration.java
index 62fb0120a97d..dba9cc95d902 100644
--- a/media/java/android/media/AudioPlaybackConfiguration.java
+++ b/media/java/android/media/AudioPlaybackConfiguration.java
@@ -283,8 +283,19 @@ public final class AudioPlaybackConfiguration implements Parcelable {
* Flag used when playback is muted by AppOpsManager#OP_PLAY_AUDIO.
*/
@SystemApi
+ @FlaggedApi(FLAG_MUTED_BY_PORT_VOLUME_API)
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ public static final int MUTED_BY_OP_PLAY_AUDIO = (1 << 3);
+ /**
+ * @hide
+ * Flag used when playback is muted by AppOpsManager#OP_PLAY_AUDIO.
+ * @deprecated see {@link MUTED_BY_OP_PLAY_AUDIO}
+ */
+ @SystemApi
+ @Deprecated
+ @FlaggedApi(FLAG_MUTED_BY_PORT_VOLUME_API)
@RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
- public static final int MUTED_BY_APP_OPS = (1 << 3);
+ public static final int MUTED_BY_APP_OPS = MUTED_BY_OP_PLAY_AUDIO;
/**
* @hide
* Flag used when muted by client volume.
@@ -315,13 +326,16 @@ public final class AudioPlaybackConfiguration implements Parcelable {
* @hide
* Flag used when playback is muted by AppOpsManager#OP_CONTROL_AUDIO.
*/
+ @SystemApi
+ @FlaggedApi(FLAG_MUTED_BY_PORT_VOLUME_API)
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
public static final int MUTED_BY_OP_CONTROL_AUDIO = (1 << 7);
/** @hide */
@IntDef(
flag = true,
value = {MUTED_BY_MASTER, MUTED_BY_STREAM_VOLUME, MUTED_BY_STREAM_MUTED,
- MUTED_BY_APP_OPS, MUTED_BY_CLIENT_VOLUME, MUTED_BY_VOLUME_SHAPER,
+ MUTED_BY_OP_PLAY_AUDIO, MUTED_BY_CLIENT_VOLUME, MUTED_BY_VOLUME_SHAPER,
MUTED_BY_PORT_VOLUME, MUTED_BY_OP_CONTROL_AUDIO})
@Retention(RetentionPolicy.SOURCE)
public @interface PlayerMuteEvent {
@@ -767,7 +781,7 @@ public final class AudioPlaybackConfiguration implements Parcelable {
private boolean isMuteAffectingActiveState() {
return (mMutedState & MUTED_BY_CLIENT_VOLUME) != 0
|| (mMutedState & MUTED_BY_VOLUME_SHAPER) != 0
- || (mMutedState & MUTED_BY_APP_OPS) != 0;
+ || (mMutedState & MUTED_BY_OP_PLAY_AUDIO) != 0;
}
/**
@@ -908,8 +922,8 @@ public final class AudioPlaybackConfiguration implements Parcelable {
if ((mMutedState & MUTED_BY_STREAM_MUTED) != 0) {
apcToString.append("streamMute ");
}
- if ((mMutedState & MUTED_BY_APP_OPS) != 0) {
- apcToString.append("appOps ");
+ if ((mMutedState & MUTED_BY_OP_PLAY_AUDIO) != 0) {
+ apcToString.append("opPlayAudio ");
}
if ((mMutedState & MUTED_BY_CLIENT_VOLUME) != 0) {
apcToString.append("clientVolume ");
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index d8a8c8b0ee2a..bbe8e4ed7b34 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -141,6 +141,14 @@ flag {
}
flag {
+ name: "enable_route_visibility_control_api"
+ namespace: "media_better_together"
+ description: "API changes to allow more control over route visibility by route providers"
+ bug: "367799834"
+ is_exported: true
+}
+
+flag {
name: "enable_screen_off_scanning"
is_exported: true
namespace: "media_solutions"
diff --git a/native/android/Android.bp b/native/android/Android.bp
index da29c49f9d7b..cd6de5a5c8f0 100644
--- a/native/android/Android.bp
+++ b/native/android/Android.bp
@@ -55,6 +55,7 @@ cc_library_shared {
"surface_control_input_receiver.cpp",
"choreographer.cpp",
"configuration.cpp",
+ "display_luts.cpp",
"dynamic_instrumentation_manager.cpp",
"hardware_buffer_jni.cpp",
"input.cpp",
diff --git a/native/android/display_luts.cpp b/native/android/display_luts.cpp
new file mode 100644
index 000000000000..179a32bd1c03
--- /dev/null
+++ b/native/android/display_luts.cpp
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#define LOG_TAG "DisplayLuts"
+
+#include <android/display_luts.h>
+#include <display_luts_private.h>
+#include <utils/Log.h>
+
+#include <cmath>
+
+#define ADISPLAYLUTS_BUFFER_LENGTH_LIMIT (100000)
+
+#define CHECK_NOT_NULL(name) \
+ LOG_ALWAYS_FATAL_IF(name == nullptr, "nullptr passed as " #name " argument");
+
+ADisplayLutsEntry* ADisplayLutsEntry_createEntry(float* buffer, int32_t length, int32_t dimension,
+ int32_t key) {
+ CHECK_NOT_NULL(buffer);
+ LOG_ALWAYS_FATAL_IF(length >= ADISPLAYLUTS_BUFFER_LENGTH_LIMIT,
+ "the lut raw buffer length is too big to handle");
+ if (dimension != ADISPLAYLUTS_ONE_DIMENSION && dimension != ADISPLAYLUTS_THREE_DIMENSION) {
+ LOG_ALWAYS_FATAL("the lut dimension is be either 1 or 3");
+ }
+ int32_t size = 0;
+ if (dimension == ADISPLAYLUTS_THREE_DIMENSION) {
+ LOG_ALWAYS_FATAL_IF(length % 3 != 0, "the 3d lut raw buffer is not divisible by 3");
+ int32_t lengthPerChannel = length / 3;
+ float sizeForDim = std::cbrt(static_cast<float>(lengthPerChannel));
+ LOG_ALWAYS_FATAL_IF(sizeForDim != (int)(sizeForDim),
+ "the 3d lut buffer length is incorrect");
+ size = (int)sizeForDim;
+ } else {
+ size = length;
+ }
+ LOG_ALWAYS_FATAL_IF(size < 2, "the lut size for each dimension is too small");
+
+ ADisplayLutsEntry* entry = new ADisplayLutsEntry();
+ entry->buffer.data.resize(length);
+ std::copy(buffer, buffer + length, entry->buffer.data.begin());
+ entry->properties = {dimension, size, key};
+
+ entry->incStrong((void*)ADisplayLutsEntry_createEntry);
+ return static_cast<ADisplayLutsEntry*>(entry);
+}
+
+void ADisplayLutsEntry_destroy(ADisplayLutsEntry* entry) {
+ if (entry != NULL) {
+ entry->decStrong((void*)ADisplayLutsEntry_createEntry);
+ }
+}
+
+ADisplayLuts_Dimension ADisplayLutsEntry_getDimension(const ADisplayLutsEntry* entry) {
+ CHECK_NOT_NULL(entry);
+ return static_cast<ADisplayLuts_Dimension>(entry->properties.dimension);
+}
+
+int32_t ADisplayLutsEntry_getSize(const ADisplayLutsEntry* entry) {
+ CHECK_NOT_NULL(entry);
+ return entry->properties.size;
+}
+
+ADisplayLuts_SamplingKey ADisplayLutsEntry_getSamplingKey(const ADisplayLutsEntry* entry) {
+ CHECK_NOT_NULL(entry);
+ return static_cast<ADisplayLuts_SamplingKey>(entry->properties.samplingKey);
+}
+
+const float* ADisplayLutsEntry_getBuffer(const ADisplayLutsEntry* _Nonnull entry) {
+ CHECK_NOT_NULL(entry);
+ return entry->buffer.data.data();
+}
+
+ADisplayLuts* ADisplayLuts_create() {
+ ADisplayLuts* luts = new ADisplayLuts();
+ if (luts == NULL) {
+ delete luts;
+ return NULL;
+ }
+ luts->incStrong((void*)ADisplayLuts_create);
+ return static_cast<ADisplayLuts*>(luts);
+}
+
+void ADisplayLuts_clearLuts(ADisplayLuts* luts) {
+ for (auto& entry : luts->entries) {
+ entry->decStrong((void*)ADisplayLuts_setEntries); // Decrement ref count
+ }
+ luts->entries.clear();
+ luts->offsets.clear();
+ luts->totalBufferSize = 0;
+}
+
+void ADisplayLuts_destroy(ADisplayLuts* luts) {
+ if (luts != NULL) {
+ ADisplayLuts_clearLuts(luts);
+ luts->decStrong((void*)ADisplayLuts_create);
+ }
+}
+
+void ADisplayLuts_setEntries(ADisplayLuts* luts, ADisplayLutsEntry** entries, int32_t numEntries) {
+ CHECK_NOT_NULL(luts);
+ // always clear the previously set lut(s)
+ ADisplayLuts_clearLuts(luts);
+
+ // do nothing
+ if (!entries || numEntries == 0) {
+ return;
+ }
+
+ LOG_ALWAYS_FATAL_IF(numEntries > 2, "The number of entries should be not over 2!");
+ if (numEntries == 2 && entries[0]->properties.dimension != ADISPLAYLUTS_ONE_DIMENSION &&
+ entries[1]->properties.dimension != ADISPLAYLUTS_THREE_DIMENSION) {
+ LOG_ALWAYS_FATAL("The entries should be 1D and 3D in order!");
+ }
+
+ luts->offsets.reserve(numEntries);
+ luts->entries.reserve(numEntries);
+ for (int32_t i = 0; i < numEntries; i++) {
+ luts->offsets.emplace_back(luts->totalBufferSize);
+ luts->totalBufferSize += entries[i]->buffer.data.size();
+ luts->entries.emplace_back(entries[i]);
+ luts->entries.back()->incStrong((void*)ADisplayLuts_setEntries);
+ }
+} \ No newline at end of file
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index 9da7becba332..7f555a868615 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -95,6 +95,15 @@ LIBANDROID {
AConfiguration_setTouchscreen;
AConfiguration_setUiModeNight;
AConfiguration_setUiModeType;
+ ADisplayLuts_create; # introduced=36
+ ADisplayLuts_setEntries; # introduced=36
+ ADisplayLuts_destroy; # introduced=36
+ ADisplayLutsEntry_createEntry; # introduced=36
+ ADisplayLutsEntry_getDimension; # introduced=36
+ ADisplayLutsEntry_getSize; # introduced=36
+ ADisplayLutsEntry_getSamplingKey; # introduced=36
+ ADisplayLutsEntry_getBuffer; # introduced=36
+ ADisplayLutsEntry_destroy; # introduced=36
AInputEvent_getDeviceId;
AInputEvent_getSource;
AInputEvent_getType;
@@ -300,6 +309,7 @@ LIBANDROID {
ASurfaceTransaction_setHdrMetadata_smpte2086; # introduced=29
ASurfaceTransaction_setExtendedRangeBrightness; # introduced=UpsideDownCake
ASurfaceTransaction_setDesiredHdrHeadroom; # introduced=VanillaIceCream
+ ASurfaceTransaction_setLuts; # introduced=36
ASurfaceTransaction_setOnComplete; # introduced=29
ASurfaceTransaction_setOnCommit; # introduced=31
ASurfaceTransaction_setPosition; # introduced=31
diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp
index 698bc84a78b9..fc64e9b48f6d 100644
--- a/native/android/surface_control.cpp
+++ b/native/android/surface_control.cpp
@@ -14,12 +14,15 @@
* limitations under the License.
*/
+#include <android/gui/LutProperties.h>
#include <android/hardware/configstore/1.0/ISurfaceFlingerConfigs.h>
#include <android/native_window.h>
#include <android/surface_control.h>
#include <android/surface_control_jni.h>
#include <android_runtime/android_view_SurfaceControl.h>
#include <configstore/Utils.h>
+#include <cutils/ashmem.h>
+#include <display_luts_private.h>
#include <gui/HdrMetadata.h>
#include <gui/ISurfaceComposer.h>
#include <gui/Surface.h>
@@ -53,6 +56,14 @@ static_assert(static_cast<int>(ADATASPACE_SCRGB) == static_cast<int>(HAL_DATASPA
static_assert(static_cast<int>(ADATASPACE_DISPLAY_P3) ==
static_cast<int>(HAL_DATASPACE_DISPLAY_P3));
static_assert(static_cast<int>(ADATASPACE_BT2020_PQ) == static_cast<int>(HAL_DATASPACE_BT2020_PQ));
+static_assert(static_cast<int>(ADISPLAYLUTS_ONE_DIMENSION) ==
+ static_cast<int>(android::gui::LutProperties::Dimension::ONE_D));
+static_assert(static_cast<int>(ADISPLAYLUTS_THREE_DIMENSION) ==
+ static_cast<int>(android::gui::LutProperties::Dimension::THREE_D));
+static_assert(static_cast<int>(ADISPLAYLUTS_SAMPLINGKEY_RGB) ==
+ static_cast<int>(android::gui::LutProperties::SamplingKey::RGB));
+static_assert(static_cast<int>(ADISPLAYLUTS_SAMPLINGKEY_MAX_RGB) ==
+ static_cast<int>(android::gui::LutProperties::SamplingKey::MAX_RGB));
Transaction* ASurfaceTransaction_to_Transaction(ASurfaceTransaction* aSurfaceTransaction) {
return reinterpret_cast<Transaction*>(aSurfaceTransaction);
@@ -693,6 +704,58 @@ void ASurfaceTransaction_setDesiredHdrHeadroom(ASurfaceTransaction* aSurfaceTran
transaction->setDesiredHdrHeadroom(surfaceControl, desiredRatio);
}
+void ASurfaceTransaction_setLuts(ASurfaceTransaction* aSurfaceTransaction,
+ ASurfaceControl* aSurfaceControl,
+ const struct ADisplayLuts* luts) {
+ CHECK_NOT_NULL(aSurfaceTransaction);
+ CHECK_NOT_NULL(aSurfaceControl);
+
+ sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl);
+ Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction);
+
+ int fd = -1;
+ std::vector<int32_t> offsets;
+ std::vector<int32_t> dimensions;
+ std::vector<int32_t> sizes;
+ std::vector<int32_t> samplingKeys;
+
+ if (luts) {
+ std::vector<float> buffer(luts->totalBufferSize);
+ int32_t count = luts->offsets.size();
+ offsets = luts->offsets;
+
+ dimensions.reserve(count);
+ sizes.reserve(count);
+ samplingKeys.reserve(count);
+ for (int32_t i = 0; i < count; i++) {
+ dimensions.emplace_back(luts->entries[i]->properties.dimension);
+ sizes.emplace_back(luts->entries[i]->properties.size);
+ samplingKeys.emplace_back(luts->entries[i]->properties.samplingKey);
+ std::copy(luts->entries[i]->buffer.data.begin(), luts->entries[i]->buffer.data.end(),
+ buffer.begin() + offsets[i]);
+ }
+
+ // mmap
+ fd = ashmem_create_region("lut_shared_mem", luts->totalBufferSize * sizeof(float));
+ if (fd < 0) {
+ LOG_ALWAYS_FATAL("setLuts, ashmem_create_region() failed");
+ return;
+ }
+ void* ptr = mmap(nullptr, luts->totalBufferSize * sizeof(float), PROT_READ | PROT_WRITE,
+ MAP_SHARED, fd, 0);
+ if (ptr == MAP_FAILED) {
+ LOG_ALWAYS_FATAL("setLuts, Failed to map the shared memory");
+ return;
+ }
+
+ memcpy(ptr, buffer.data(), luts->totalBufferSize * sizeof(float));
+ munmap(ptr, luts->totalBufferSize * sizeof(float));
+ }
+
+ transaction->setLuts(surfaceControl, base::unique_fd(fd), offsets, dimensions, sizes,
+ samplingKeys);
+}
+
void ASurfaceTransaction_setColor(ASurfaceTransaction* aSurfaceTransaction,
ASurfaceControl* aSurfaceControl,
float r, float g, float b, float alpha,
diff --git a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
index c5fb80840b84..b00658052bde 100644
--- a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
+++ b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
@@ -73,13 +73,13 @@ public:
MOCK_METHOD(ScopedAStatus, closeSessionChannel, (), (override));
MOCK_METHOD(ScopedAStatus, getCpuHeadroom,
(const ::aidl::android::os::CpuHeadroomParamsInternal& in_params,
- std::vector<float>* _aidl_return),
+ std::optional<hal::CpuHeadroomResult>* _aidl_return),
(override));
MOCK_METHOD(ScopedAStatus, getCpuHeadroomMinIntervalMillis, (int64_t* _aidl_return),
(override));
MOCK_METHOD(ScopedAStatus, getGpuHeadroom,
(const ::aidl::android::os::GpuHeadroomParamsInternal& in_params,
- float* _aidl_return),
+ std::optional<hal::GpuHeadroomResult>* _aidl_return),
(override));
MOCK_METHOD(ScopedAStatus, getGpuHeadroomMinIntervalMillis, (int64_t* _aidl_return),
(override));
diff --git a/nfc/tests/src/android/nfc/NdefMessageTest.java b/nfc/tests/src/android/nfc/NdefMessageTest.java
new file mode 100644
index 000000000000..9ca295da75c3
--- /dev/null
+++ b/nfc/tests/src/android/nfc/NdefMessageTest.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.nfc;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class NdefMessageTest {
+ private NdefMessage mNdefMessage;
+ private NdefRecord mNdefRecord;
+
+ @Before
+ public void setUp() {
+ mNdefRecord = NdefRecord.createUri("http://www.example.com");
+ mNdefMessage = new NdefMessage(mNdefRecord);
+ }
+
+ @After
+ public void tearDown() {
+ }
+
+ @Test
+ public void testGetRecords() {
+ NdefRecord[] records = mNdefMessage.getRecords();
+ assertThat(records).isNotNull();
+ assertThat(records).hasLength(1);
+ assertThat(records[0]).isEqualTo(mNdefRecord);
+ }
+
+ @Test
+ public void testToByteArray() throws FormatException {
+ byte[] bytes = mNdefMessage.toByteArray();
+ assertThat(bytes).isNotNull();
+ assertThat(bytes.length).isGreaterThan(0);
+ NdefMessage ndefMessage = new NdefMessage(bytes);
+ assertThat(ndefMessage).isNotNull();
+ }
+}
diff --git a/packages/SettingsLib/IntroPreference/src/com/android/settingslib/widget/IntroPreference.kt b/packages/SettingsLib/IntroPreference/src/com/android/settingslib/widget/IntroPreference.kt
index 81337612e59a..f9931cf3238d 100644
--- a/packages/SettingsLib/IntroPreference/src/com/android/settingslib/widget/IntroPreference.kt
+++ b/packages/SettingsLib/IntroPreference/src/com/android/settingslib/widget/IntroPreference.kt
@@ -68,6 +68,7 @@ class IntroPreference @JvmOverloads constructor(
(holder.findViewById(R.id.collapsable_summary) as? CollapsableTextView)?.apply {
setCollapsable(isCollapsable)
setMinLines(minLines)
+ visibility = if (summary.isNullOrEmpty()) View.GONE else View.VISIBLE
setText(summary.toString())
if (hyperlinkListener != null) {
setHyperlinkListener(hyperlinkListener)
diff --git a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java
index 83858d9c9c54..bc62e565599c 100644
--- a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java
+++ b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java
@@ -141,7 +141,7 @@ public class MainSwitchPreference extends TwoStatePreference
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
- super.setChecked(isChecked);
+ buttonView.post(() -> super.setChecked(isChecked));
}
/**
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceDataStoreAdapter.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceDataStoreAdapter.kt
index c2728b4ad012..7601b9a31041 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceDataStoreAdapter.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceDataStoreAdapter.kt
@@ -20,7 +20,7 @@ import androidx.preference.PreferenceDataStore
import com.android.settingslib.datastore.KeyValueStore
/** Adapter to translate [KeyValueStore] into [PreferenceDataStore]. */
-class PreferenceDataStoreAdapter(private val keyValueStore: KeyValueStore) : PreferenceDataStore() {
+class PreferenceDataStoreAdapter(val keyValueStore: KeyValueStore) : PreferenceDataStore() {
override fun getBoolean(key: String, defValue: Boolean): Boolean =
keyValueStore.getValue(key, Boolean::class.javaObjectType) ?: defValue
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt
index 62ac3ade62a4..7cec59c9090c 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt
@@ -74,16 +74,12 @@ class PreferenceScreenBindingHelper(
private val preferences: ImmutableMap<String, PreferenceHierarchyNode>
private val dependencies: ImmutableMultimap<String, String>
private val lifecycleAwarePreferences: Array<PreferenceLifecycleProvider>
- private val storages = mutableSetOf<KeyedObservable<String>>()
+ private val storages = mutableMapOf<String, KeyedObservable<String>>()
private val preferenceObserver: KeyedObserver<String?>
private val storageObserver =
- KeyedObserver<String?> { key, _ ->
- if (key != null) {
- notifyChange(key, CHANGE_REASON_VALUE)
- }
- }
+ KeyedObserver<String> { key, _ -> notifyChange(key, CHANGE_REASON_VALUE) }
init {
val preferencesBuilder = ImmutableMap.builder<String, PreferenceHierarchyNode>()
@@ -98,7 +94,6 @@ class PreferenceScreenBindingHelper(
preferencesBuilder.put(it.key, this)
it.dependencyOfEnabledState(context)?.addDependency(it)
if (it is PreferenceLifecycleProvider) lifecycleAwarePreferences.add(it)
- if (it is PersistentPreference<*>) storages.add(it.storage(context))
}
}
@@ -120,7 +115,16 @@ class PreferenceScreenBindingHelper(
preferenceObserver = KeyedObserver { key, reason -> onPreferenceChange(key, reason) }
addObserver(preferenceObserver, mainExecutor)
- for (storage in storages) storage.addObserver(storageObserver, mainExecutor)
+
+ preferenceScreen.forEachRecursively {
+ val preferenceDataStore = it.preferenceDataStore
+ if (preferenceDataStore is PreferenceDataStoreAdapter) {
+ val key = it.key
+ val keyValueStore = preferenceDataStore.keyValueStore
+ storages[key] = keyValueStore
+ keyValueStore.addObserver(key, storageObserver, mainExecutor)
+ }
+ }
}
private fun onPreferenceChange(key: String?, reason: Int) {
@@ -181,7 +185,7 @@ class PreferenceScreenBindingHelper(
fun onDestroy() {
removeObserver(preferenceObserver)
- for (storage in storages) storage.removeObserver(storageObserver)
+ for ((key, storage) in storages) storage.removeObserver(key, storageObserver)
for (preference in lifecycleAwarePreferences) {
preference.onDestroy(preferenceLifecycleContext)
}
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/Utils.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/Utils.kt
new file mode 100644
index 000000000000..2e7221bd4d7f
--- /dev/null
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/Utils.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.preference
+
+import androidx.preference.Preference
+import androidx.preference.PreferenceGroup
+
+/** Traversals preference hierarchy recursively and applies an action. */
+fun PreferenceGroup.forEachRecursively(action: (Preference) -> Unit) {
+ action.invoke(this)
+ for (index in 0 until preferenceCount) {
+ val preference = getPreference(index)
+ if (preference is PreferenceGroup) {
+ preference.forEachRecursively(action)
+ } else {
+ action.invoke(preference)
+ }
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastCallbackExt.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastCallbackExt.kt
index 07abb6b912b6..888f54f4bb34 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastCallbackExt.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastCallbackExt.kt
@@ -69,8 +69,8 @@ val LocalBluetoothLeBroadcast.onBroadcastStartedOrStopped: Flow<Unit>
}
.buffer(capacity = Channel.CONFLATED)
-/** [Flow] for [BluetoothLeBroadcast.Callback] onPlaybackStarted event */
-val LocalBluetoothLeBroadcast.onPlaybackStarted: Flow<Unit>
+/** [Flow] for [BluetoothLeBroadcast.Callback] onBroadcastMetadataChanged event */
+val LocalBluetoothLeBroadcast.onBroadcastMetadataChanged: Flow<Unit>
get() =
callbackFlow {
val listener =
@@ -87,7 +87,6 @@ val LocalBluetoothLeBroadcast.onPlaybackStarted: Flow<Unit>
}
override fun onPlaybackStarted(reason: Int, broadcastId: Int) {
- launch { trySend(Unit) }
}
override fun onPlaybackStopped(reason: Int, broadcastId: Int) {
@@ -100,7 +99,9 @@ val LocalBluetoothLeBroadcast.onPlaybackStarted: Flow<Unit>
override fun onBroadcastMetadataChanged(
broadcastId: Int,
metadata: BluetoothLeBroadcastMetadata
- ) {}
+ ) {
+ trySend(Unit)
+ }
}
registerServiceCallBack(
ConcurrentUtils.DIRECT_EXECUTOR,
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 03fea37deaf5..6128d45831fb 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -130,10 +130,12 @@ import com.google.android.collect.Sets;
import libcore.util.HexEncoding;
+import java.io.BufferedReader;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
+import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -380,6 +382,8 @@ public class SettingsProvider extends ContentProvider {
@GuardedBy("mLock")
private Handler mHandler;
+ private static final Set<String> sDeviceConfigAllowlistedNamespaces = new ArraySet<>();
+
// We have to call in the user manager with no lock held,
private volatile UserManager mUserManager;
@@ -2442,6 +2446,10 @@ public class SettingsProvider extends ContentProvider {
if (!isRestrictedShell && hasWritePermission) {
assertCallingUserDenyList(flags);
} else if (hasAllowlistPermission) {
+ Set<String> allowlistedDeviceConfigNamespaces = null;
+ if (isRestrictedShell) {
+ allowlistedDeviceConfigNamespaces = getAllowlistedDeviceConfigNamespaces();
+ }
for (String flag : flags) {
boolean namespaceAllowed = false;
if (isRestrictedShell) {
@@ -2452,7 +2460,7 @@ public class SettingsProvider extends ContentProvider {
} else {
flagNamespace = flag;
}
- if (WritableNamespaces.ALLOWLIST.contains(flagNamespace)) {
+ if (allowlistedDeviceConfigNamespaces.contains(flagNamespace)) {
namespaceAllowed = true;
}
} else {
@@ -2513,6 +2521,60 @@ public class SettingsProvider extends ContentProvider {
}
}
+ /**
+ * Returns a Set of DeviceConfig allowlisted namespaces in which all flags can be modified
+ * by a caller with the {@code WRITE_ALLOWLISTED_DEVICE_CONFIG} permission.
+ * <p>
+ * This method also supports mainline modules that introduce their own allowlisted
+ * namespaces within the {@code etc/writable_namespaces} file under their directory.
+ */
+ private Set<String> getAllowlistedDeviceConfigNamespaces() {
+ synchronized (sDeviceConfigAllowlistedNamespaces) {
+ if (!sDeviceConfigAllowlistedNamespaces.isEmpty()) {
+ return sDeviceConfigAllowlistedNamespaces;
+ }
+ if (android.provider.flags.Flags.deviceConfigWritableNamespacesApi()) {
+ sDeviceConfigAllowlistedNamespaces.addAll(DeviceConfig.getAdbWritableNamespaces());
+ } else {
+ sDeviceConfigAllowlistedNamespaces.addAll(WritableNamespaces.ALLOWLIST);
+ }
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ List<String> apexDirectories;
+ try {
+ apexDirectories = mPackageManager.getAllApexDirectories();
+ } catch (RemoteException e) {
+ Slog.e(LOG_TAG, "Caught a RemoteException obtaining APEX directories: ", e);
+ return sDeviceConfigAllowlistedNamespaces;
+ }
+ for (int i = 0; i < apexDirectories.size(); i++) {
+ String apexDirectory = apexDirectories.get(i);
+ File namespaceFile = Environment.buildPath(new File(apexDirectory), "etc",
+ "writable_namespaces");
+ if (namespaceFile.exists() && namespaceFile.isFile()) {
+ try (BufferedReader reader = new BufferedReader(
+ new FileReader(namespaceFile))) {
+ String namespace;
+ while ((namespace = reader.readLine()) != null) {
+ namespace = namespace.trim();
+ // Support comments by ignoring any lines that start with '#'.
+ if (!namespace.isEmpty() && !namespace.startsWith("#")) {
+ sDeviceConfigAllowlistedNamespaces.add(namespace);
+ }
+ }
+ } catch (IOException e) {
+ Slog.e(LOG_TAG, "Caught an exception parsing file: " + namespaceFile,
+ e);
+ }
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ return sDeviceConfigAllowlistedNamespaces;
+ }
+ }
+
private static void warnOrThrowForUndesiredSecureSettingsMutationForTargetSdk(
int targetSdkVersion, String name) {
// If the app targets Lollipop MR1 or older SDK we warn, otherwise crash.
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/WritableNamespaces.java b/packages/SettingsProvider/src/com/android/providers/settings/WritableNamespaces.java
index b0409c037107..5ce97eb22a04 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/WritableNamespaces.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/WritableNamespaces.java
@@ -33,6 +33,7 @@ import java.util.Set;
final class WritableNamespaces {
public static final Set<String> ALLOWLIST =
new ArraySet<String>(Arrays.asList(
+ "adservices",
"captive_portal_login",
"connectivity",
"exo",
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 27e6bab1642d..0ec5571a7b8f 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -151,7 +151,8 @@
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<uses-permission android:name="android.permission.LOCATION_BYPASS" />
<uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
- <uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG" />
+ <uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG"
+ android:featureFlag="!android.security.protect_device_config_flags"/>
<uses-permission android:name="android.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG" />
<uses-permission android:name="android.permission.READ_WRITE_SYNC_DISABLED_MODE_CONFIG" />
<uses-permission android:name="android.permission.MONITOR_DEVICE_CONFIG_ACCESS" />
diff --git a/packages/StatementService/Android.bp b/packages/StatementService/Android.bp
index 90e1808b7d44..39b0302beff8 100644
--- a/packages/StatementService/Android.bp
+++ b/packages/StatementService/Android.bp
@@ -38,8 +38,10 @@ android_app {
"StatementServiceParser",
"androidx.appcompat_appcompat",
"androidx.collection_collection-ktx",
+ "androidx.room_room-runtime",
"androidx.work_work-runtime",
"androidx.work_work-runtime-ktx",
"kotlinx-coroutines-android",
],
+ plugins: ["androidx.room_room-compiler-plugin"],
}
diff --git a/packages/StatementService/src/com/android/statementservice/database/Converters.kt b/packages/StatementService/src/com/android/statementservice/database/Converters.kt
new file mode 100644
index 000000000000..21ecc8b4a651
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/database/Converters.kt
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.statementservice.database
+
+import android.content.UriRelativeFilter
+import android.content.UriRelativeFilterGroup
+import android.util.JsonReader
+import androidx.room.TypeConverter
+import org.json.JSONArray
+import org.json.JSONObject
+import java.io.StringReader
+import java.util.ArrayList
+
+class Converters {
+ companion object {
+ private const val ACTION_NAME = "action"
+ private const val FILTERS_NAME = "filters"
+ private const val URI_PART_NAME = "uriPart"
+ private const val PATTERN_TYPE_NAME = "patternType"
+ private const val FILTER_NAME = "filter"
+ }
+
+ @TypeConverter
+ fun groupsToJson(groups: List<UriRelativeFilterGroup>): String {
+ val json = JSONArray()
+ for (group in groups) {
+ json.put(groupToJson(group))
+ }
+ return json.toString()
+ }
+
+ @TypeConverter
+ fun stringToGroups(json: String): List<UriRelativeFilterGroup> {
+ val groups = ArrayList<UriRelativeFilterGroup>()
+ StringReader(json).use { stringReader ->
+ JsonReader(stringReader).use { reader ->
+ reader.beginArray()
+ while (reader.hasNext()) {
+ groups.add(parseGroup(reader))
+ }
+ reader.endArray()
+ }
+ }
+ return groups
+ }
+
+ private fun groupToJson(group: UriRelativeFilterGroup): JSONObject {
+ val jsonObject = JSONObject()
+ jsonObject.put(ACTION_NAME, group.action)
+ val filters = JSONArray()
+ for (filter in group.uriRelativeFilters) {
+ filters.put(filterToJson(filter))
+ }
+ jsonObject.put(FILTERS_NAME, filters)
+ return jsonObject
+ }
+
+ private fun filterToJson(filter: UriRelativeFilter): JSONObject {
+ val jsonObject = JSONObject()
+ jsonObject.put(URI_PART_NAME, filter.uriPart)
+ jsonObject.put(PATTERN_TYPE_NAME, filter.patternType)
+ jsonObject.put(FILTER_NAME, filter.filter)
+ return jsonObject
+ }
+
+ private fun parseGroup(reader: JsonReader): UriRelativeFilterGroup {
+ val jsonObject = JSONObject()
+ reader.beginObject()
+ while (reader.hasNext()) {
+ val name = reader.nextName()
+ when (name) {
+ ACTION_NAME -> jsonObject.put(ACTION_NAME, reader.nextInt())
+ FILTERS_NAME -> jsonObject.put(FILTERS_NAME, parseFilters(reader))
+ else -> reader.skipValue()
+ }
+ }
+ reader.endObject()
+
+ val group = UriRelativeFilterGroup(jsonObject.getInt(ACTION_NAME))
+ val filters = jsonObject.getJSONArray(FILTERS_NAME)
+ for (i in 0 until filters.length()) {
+ val filter = filters.getJSONObject(i)
+ group.addUriRelativeFilter(UriRelativeFilter(
+ filter.getInt(URI_PART_NAME),
+ filter.getInt(PATTERN_TYPE_NAME),
+ filter.getString(FILTER_NAME)
+ ))
+ }
+ return group
+ }
+
+ private fun parseFilters(reader: JsonReader): JSONArray {
+ val filters = JSONArray()
+ reader.beginArray()
+ while (reader.hasNext()) {
+ filters.put(parseFilter(reader))
+ }
+ reader.endArray()
+ return filters
+ }
+
+ private fun parseFilter(reader: JsonReader): JSONObject {
+ reader.beginObject()
+ val jsonObject = JSONObject()
+ while (reader.hasNext()) {
+ val name = reader.nextName()
+ when (name) {
+ URI_PART_NAME, PATTERN_TYPE_NAME -> jsonObject.put(name, reader.nextInt())
+ FILTER_NAME -> jsonObject.put(name, reader.nextString())
+ else -> reader.skipValue()
+ }
+ }
+ reader.endObject()
+ return jsonObject
+ }
+} \ No newline at end of file
diff --git a/packages/StatementService/src/com/android/statementservice/database/DomainGroups.kt b/packages/StatementService/src/com/android/statementservice/database/DomainGroups.kt
new file mode 100644
index 000000000000..c61666910cb4
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/database/DomainGroups.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.statementservice.database
+
+import android.content.UriRelativeFilterGroup
+import androidx.room.Entity
+
+@Entity(primaryKeys = ["packageName", "domain"])
+data class DomainGroups(
+ val packageName: String,
+ val domain: String,
+ val groups: List<UriRelativeFilterGroup>
+) \ No newline at end of file
diff --git a/packages/StatementService/src/com/android/statementservice/database/DomainGroupsDao.kt b/packages/StatementService/src/com/android/statementservice/database/DomainGroupsDao.kt
new file mode 100644
index 000000000000..3b4dcea48180
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/database/DomainGroupsDao.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.statementservice.database
+
+import androidx.room.Dao
+import androidx.room.Insert
+import androidx.room.Query
+
+@Dao
+interface DomainGroupsDao {
+ @Query("SELECT * FROM DomainGroups WHERE packageName = :packageName")
+ fun getDomainGroups(packageName: String): List<DomainGroups>
+
+ @Insert
+ fun insertDomainGroups(vararg domainGroups: DomainGroups)
+
+ @Query("DELETE FROM DomainGroups WHERE packageName = :packageName AND domain = :domain")
+ fun clear(packageName: String, domain: String)
+
+ @Query("DELETE FROM DomainGroups WHERE packageName = :packageName")
+ fun clear(packageName: String)
+} \ No newline at end of file
diff --git a/packages/StatementService/src/com/android/statementservice/database/DomainGroupsDatabase.kt b/packages/StatementService/src/com/android/statementservice/database/DomainGroupsDatabase.kt
new file mode 100644
index 000000000000..39833f6bc80b
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/database/DomainGroupsDatabase.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.statementservice.database
+
+import android.content.Context
+import androidx.room.Database
+import androidx.room.Room
+import androidx.room.RoomDatabase
+import androidx.room.TypeConverters
+
+@Database(entities = [DomainGroups::class], version = 1)
+@TypeConverters(Converters::class)
+abstract class DomainGroupsDatabase : RoomDatabase() {
+ companion object {
+ private const val DATABASE_NAME = "domain-groups"
+ @Volatile
+ private var instance: DomainGroupsDatabase? = null
+
+ fun getInstance(context: Context) = instance ?: synchronized(this) {
+ instance ?: Room.databaseBuilder(
+ context,
+ DomainGroupsDatabase::class.java, DATABASE_NAME
+ ).build().also { instance = it }
+ }
+ }
+ abstract fun domainGroupsDao(): DomainGroupsDao
+} \ No newline at end of file
diff --git a/packages/StatementService/src/com/android/statementservice/domain/DomainVerificationReceiverV1.kt b/packages/StatementService/src/com/android/statementservice/domain/DomainVerificationReceiverV1.kt
index acb54f6093de..0d7a1fdbcfb8 100644
--- a/packages/StatementService/src/com/android/statementservice/domain/DomainVerificationReceiverV1.kt
+++ b/packages/StatementService/src/com/android/statementservice/domain/DomainVerificationReceiverV1.kt
@@ -22,6 +22,7 @@ import android.content.pm.PackageManager
import androidx.work.ExistingWorkPolicy
import androidx.work.WorkManager
import com.android.statementservice.domain.worker.CollectV1Worker
+import com.android.statementservice.domain.worker.GroupUpdateV1Worker
import com.android.statementservice.domain.worker.SingleV1RequestWorker
/**
@@ -67,7 +68,7 @@ class DomainVerificationReceiverV1 : BaseDomainVerificationReceiver() {
}
}
- //clear sp before enqueue unique work since policy is REPLACE
+ // clear sp before enqueue unique work since policy is REPLACE
val deContext = context.createDeviceProtectedStorageContext()
val editor = deContext?.getSharedPreferences(packageName, Context.MODE_PRIVATE)?.edit()
editor?.clear()?.apply()
@@ -78,6 +79,7 @@ class DomainVerificationReceiverV1 : BaseDomainVerificationReceiver() {
workRequests
)
.then(CollectV1Worker.buildRequest(verificationId, packageName))
+ .then(GroupUpdateV1Worker.buildRequest(packageName))
.enqueue()
}
}
diff --git a/packages/StatementService/src/com/android/statementservice/domain/DomainVerifier.kt b/packages/StatementService/src/com/android/statementservice/domain/DomainVerifier.kt
index 29f844fb1a5d..6914347544de 100644
--- a/packages/StatementService/src/com/android/statementservice/domain/DomainVerifier.kt
+++ b/packages/StatementService/src/com/android/statementservice/domain/DomainVerifier.kt
@@ -24,6 +24,7 @@ import androidx.collection.LruCache
import com.android.statementservice.network.retriever.StatementRetriever
import com.android.statementservice.retriever.AbstractAsset
import com.android.statementservice.retriever.AbstractAssetMatcher
+import com.android.statementservice.retriever.Statement
import com.android.statementservice.utils.Result
import com.android.statementservice.utils.StatementUtils
import com.android.statementservice.utils.component1
@@ -87,10 +88,10 @@ class DomainVerifier private constructor(
host: String,
packageName: String,
network: Network? = null
- ): Pair<WorkResult, VerifyStatus> {
+ ): Triple<WorkResult, VerifyStatus, Statement?> {
val assetMatcher = synchronized(targetAssetCache) { targetAssetCache[packageName] }
.takeIf { it!!.isPresent }
- ?: return WorkResult.failure() to VerifyStatus.FAILURE_PACKAGE_MANAGER
+ ?: return Triple(WorkResult.failure(), VerifyStatus.FAILURE_PACKAGE_MANAGER, null)
return verifyHost(host, assetMatcher.get(), network)
}
@@ -98,34 +99,34 @@ class DomainVerifier private constructor(
host: String,
assetMatcher: AbstractAssetMatcher,
network: Network? = null
- ): Pair<WorkResult, VerifyStatus> {
+ ): Triple<WorkResult, VerifyStatus, Statement?> {
var exception: Exception? = null
val resultAndStatus = try {
val sourceAsset = StatementUtils.createWebAssetString(host)
.let(AbstractAsset::create)
val result = retriever.retrieve(sourceAsset, network)
- ?: return WorkResult.success() to VerifyStatus.FAILURE_UNKNOWN
+ ?: return Triple(WorkResult.success(), VerifyStatus.FAILURE_UNKNOWN, null)
when (result.responseCode) {
HttpURLConnection.HTTP_MOVED_PERM,
HttpURLConnection.HTTP_MOVED_TEMP -> {
- WorkResult.failure() to VerifyStatus.FAILURE_REDIRECT
+ Triple(WorkResult.failure(), VerifyStatus.FAILURE_REDIRECT, null)
}
else -> {
- val isVerified = result.statements.any { statement ->
+ val statement = result.statements.firstOrNull { statement ->
(StatementUtils.RELATION.matches(statement.relation) &&
assetMatcher.matches(statement.target))
}
- if (isVerified) {
- WorkResult.success() to VerifyStatus.SUCCESS
+ if (statement != null) {
+ Triple(WorkResult.success(), VerifyStatus.SUCCESS, statement)
} else {
- WorkResult.failure() to VerifyStatus.FAILURE_REJECTED_BY_SERVER
+ Triple(WorkResult.failure(), VerifyStatus.FAILURE_REJECTED_BY_SERVER, statement)
}
}
}
} catch (e: Exception) {
exception = e
- WorkResult.retry() to VerifyStatus.FAILURE_UNKNOWN
+ Triple(WorkResult.retry(), VerifyStatus.FAILURE_UNKNOWN, null)
}
if (DEBUG) {
diff --git a/packages/StatementService/src/com/android/statementservice/domain/worker/BaseRequestWorker.kt b/packages/StatementService/src/com/android/statementservice/domain/worker/BaseRequestWorker.kt
index a17f9c9186ff..64d2d98b931b 100644
--- a/packages/StatementService/src/com/android/statementservice/domain/worker/BaseRequestWorker.kt
+++ b/packages/StatementService/src/com/android/statementservice/domain/worker/BaseRequestWorker.kt
@@ -17,9 +17,12 @@
package com.android.statementservice.domain.worker
import android.content.Context
+import android.content.UriRelativeFilterGroup
+import android.content.pm.verify.domain.DomainVerificationInfo
import android.content.pm.verify.domain.DomainVerificationManager
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
+import com.android.statementservice.database.DomainGroupsDatabase
import com.android.statementservice.domain.DomainVerifier
abstract class BaseRequestWorker(
@@ -27,8 +30,19 @@ abstract class BaseRequestWorker(
protected val params: WorkerParameters
) : CoroutineWorker(appContext, params) {
+ protected val database = DomainGroupsDatabase.getInstance(appContext).domainGroupsDao()
+
protected val verificationManager =
appContext.getSystemService(DomainVerificationManager::class.java)!!
protected val verifier = DomainVerifier.getInstance(appContext)
+
+ protected fun updateUriRelativeFilterGroups(packageName: String, domainGroupUpdates: Map<String, List<UriRelativeFilterGroup>>) {
+ val verifiedDomains = verificationManager.getDomainVerificationInfo(packageName)?.hostToStateMap?.filterValues {
+ it == DomainVerificationInfo.STATE_SUCCESS || it == DomainVerificationInfo.STATE_MODIFIABLE_VERIFIED
+ }?.keys?.toList() ?: emptyList()
+ val domainGroups = verificationManager.getUriRelativeFilterGroups(packageName, verifiedDomains)
+ domainGroupUpdates.forEach { (domain, groups) -> domainGroups[domain] = groups }
+ verificationManager.setUriRelativeFilterGroups(packageName, domainGroups)
+ }
}
diff --git a/packages/StatementService/src/com/android/statementservice/domain/worker/GroupUpdateV1Worker.kt b/packages/StatementService/src/com/android/statementservice/domain/worker/GroupUpdateV1Worker.kt
new file mode 100644
index 000000000000..f53dfc47acaa
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/domain/worker/GroupUpdateV1Worker.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.statementservice.domain.worker
+
+import android.content.Context
+import androidx.work.Data
+import androidx.work.OneTimeWorkRequestBuilder
+import androidx.work.WorkerParameters
+import kotlinx.coroutines.coroutineScope
+
+class GroupUpdateV1Worker(appContext: Context, params: WorkerParameters) :
+ BaseRequestWorker(appContext, params) {
+
+ companion object {
+
+ private const val PACKAGE_NAME_KEY = "packageName"
+
+ fun buildRequest(packageName: String) = OneTimeWorkRequestBuilder<GroupUpdateV1Worker>()
+ .setInputData(
+ Data.Builder()
+ .putString(PACKAGE_NAME_KEY, packageName)
+ .build()
+ )
+ .build()
+ }
+
+ override suspend fun doWork() = coroutineScope {
+ val packageName = params.inputData.getString(PACKAGE_NAME_KEY)!!
+ updateUriRelativeFilterGroups(packageName)
+ Result.success()
+ }
+
+ private fun updateUriRelativeFilterGroups(packageName: String) {
+ val groupUpdates = database.getDomainGroups(packageName)
+ updateUriRelativeFilterGroups(
+ packageName,
+ groupUpdates.associateBy({it.domain}, {it.groups})
+ )
+ database.clear(packageName)
+ }
+} \ No newline at end of file
diff --git a/packages/StatementService/src/com/android/statementservice/domain/worker/RetryRequestWorker.kt b/packages/StatementService/src/com/android/statementservice/domain/worker/RetryRequestWorker.kt
index 61ab2c264e6a..f83601a7807b 100644
--- a/packages/StatementService/src/com/android/statementservice/domain/worker/RetryRequestWorker.kt
+++ b/packages/StatementService/src/com/android/statementservice/domain/worker/RetryRequestWorker.kt
@@ -17,10 +17,13 @@
package com.android.statementservice.domain.worker
import android.content.Context
+import android.content.UriRelativeFilterGroup
+import android.content.pm.verify.domain.DomainVerificationManager
import androidx.work.NetworkType
import androidx.work.WorkerParameters
import com.android.statementservice.domain.VerifyStatus
import com.android.statementservice.utils.AndroidUtils
+import com.android.statementservice.utils.StatementUtils
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
@@ -36,7 +39,13 @@ class RetryRequestWorker(
params: WorkerParameters
) : BaseRequestWorker(appContext, params) {
- data class VerifyResult(val domainSetId: UUID, val host: String, val status: VerifyStatus)
+ data class VerifyResult(
+ val domainSetId: UUID,
+ val host: String,
+ val status: VerifyStatus,
+ val packageName: String,
+ val groups: List<UriRelativeFilterGroup>
+ )
override suspend fun doWork() = coroutineScope {
if (!AndroidUtils.isReceiverV2Enabled(appContext)) {
@@ -49,8 +58,11 @@ class RetryRequestWorker(
.map { (domainSetId, packageName, host) ->
async {
if (isActive && !isStopped) {
- val (_, status) = verifier.verifyHost(host, packageName, params.network)
- VerifyResult(domainSetId, host, status)
+ val (_, status, statement) = verifier.verifyHost(host, packageName, params.network)
+ val groups = statement?.dynamicAppLinkComponents.orEmpty().map {
+ StatementUtils.createUriRelativeFilterGroup(it)
+ }
+ VerifyResult(domainSetId, host, status, packageName, groups)
} else {
// If the job gets cancelled, stop the remaining hosts, but continue the
// job to commit the results for hosts that were already requested.
@@ -60,17 +72,25 @@ class RetryRequestWorker(
}
.awaitAll()
.filterNotNull() // TODO(b/159952358): Fast fail packages which can't be retrieved.
- .groupBy { it.domainSetId }
- .forEach { (domainSetId, resultsById) ->
- resultsById.groupBy { it.status }
- .mapValues { it.value.map(VerifyResult::host).toSet() }
- .forEach { (status, hosts) ->
- verificationManager.setDomainVerificationStatus(
- domainSetId,
- hosts,
- status.value
- )
+ .groupBy { it.packageName }
+ .forEach { (packageName, resultsByName) ->
+ val groupUpdates = mutableMapOf<String, List<UriRelativeFilterGroup>>()
+ resultsByName.groupBy { it.domainSetId }
+ .forEach { (domainSetId, resultsById) ->
+ resultsById.groupBy { it.status }
+ .forEach { (status, verifyResults) ->
+ val error = verificationManager.setDomainVerificationStatus(
+ domainSetId,
+ verifyResults.map(VerifyResult::host).toSet(),
+ status.value
+ )
+ if (error == DomainVerificationManager.STATUS_OK
+ && status == VerifyStatus.SUCCESS) {
+ verifyResults.forEach { groupUpdates[it.host] = it.groups }
+ }
+ }
}
+ updateUriRelativeFilterGroups(packageName, groupUpdates)
}
// Succeed regardless of results since this retry is best effort and not required
diff --git a/packages/StatementService/src/com/android/statementservice/domain/worker/SingleV1RequestWorker.kt b/packages/StatementService/src/com/android/statementservice/domain/worker/SingleV1RequestWorker.kt
index 7a198cb59ca4..253a162a73a2 100644
--- a/packages/StatementService/src/com/android/statementservice/domain/worker/SingleV1RequestWorker.kt
+++ b/packages/StatementService/src/com/android/statementservice/domain/worker/SingleV1RequestWorker.kt
@@ -22,7 +22,9 @@ import androidx.work.Data
import androidx.work.OneTimeWorkRequest
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkerParameters
+import com.android.statementservice.database.DomainGroups
import com.android.statementservice.utils.AndroidUtils
+import com.android.statementservice.utils.StatementUtils
import kotlinx.coroutines.coroutineScope
class SingleV1RequestWorker(appContext: Context, params: WorkerParameters) :
@@ -60,7 +62,9 @@ class SingleV1RequestWorker(appContext: Context, params: WorkerParameters) :
val packageName = params.inputData.getString(PACKAGE_NAME_KEY)!!
val host = params.inputData.getString(HOST_KEY)!!
- val (result, status) = verifier.verifyHost(host, packageName, params.network)
+ database.clear(packageName, host)
+
+ val (result, status, statement) = verifier.verifyHost(host, packageName, params.network)
if (DEBUG) {
Log.d(
@@ -75,6 +79,10 @@ class SingleV1RequestWorker(appContext: Context, params: WorkerParameters) :
val deContext = appContext.createDeviceProtectedStorageContext()
val sp = deContext?.getSharedPreferences(packageName, Context.MODE_PRIVATE)
sp?.edit()?.putInt("$HOST_SUCCESS_PREFIX$host", status.value)?.apply()
+ val groups = statement?.dynamicAppLinkComponents.orEmpty().map {
+ StatementUtils.createUriRelativeFilterGroup(it)
+ }
+ database.insertDomainGroups(DomainGroups(packageName, host, groups))
Result.success()
}
is Result.Failure -> {
diff --git a/packages/StatementService/src/com/android/statementservice/domain/worker/SingleV2RequestWorker.kt b/packages/StatementService/src/com/android/statementservice/domain/worker/SingleV2RequestWorker.kt
index 562b132d36d6..8b1347a69932 100644
--- a/packages/StatementService/src/com/android/statementservice/domain/worker/SingleV2RequestWorker.kt
+++ b/packages/StatementService/src/com/android/statementservice/domain/worker/SingleV2RequestWorker.kt
@@ -22,6 +22,7 @@ import androidx.work.OneTimeWorkRequest
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkerParameters
import com.android.statementservice.utils.AndroidUtils
+import com.android.statementservice.utils.StatementUtils
import kotlinx.coroutines.coroutineScope
import java.util.UUID
@@ -59,9 +60,13 @@ class SingleV2RequestWorker(appContext: Context, params: WorkerParameters) :
val packageName = params.inputData.getString(PACKAGE_NAME_KEY)!!
val host = params.inputData.getString(HOST_KEY)!!
- val (result, status) = verifier.verifyHost(host, packageName, params.network)
+ val (result, status, statement) = verifier.verifyHost(host, packageName, params.network)
verificationManager.setDomainVerificationStatus(domainSetId, setOf(host), status.value)
+ val groups = statement?.dynamicAppLinkComponents.orEmpty().map {
+ StatementUtils.createUriRelativeFilterGroup(it)
+ }
+ updateUriRelativeFilterGroups(packageName, mapOf(host to groups))
result
}
diff --git a/packages/StatementService/src/com/android/statementservice/network/retriever/StatementParser.kt b/packages/StatementService/src/com/android/statementservice/network/retriever/StatementParser.kt
index ad137400fa86..d10cb0f91c11 100644
--- a/packages/StatementService/src/com/android/statementservice/network/retriever/StatementParser.kt
+++ b/packages/StatementService/src/com/android/statementservice/network/retriever/StatementParser.kt
@@ -39,6 +39,11 @@ object StatementParser {
private const val FIELD_NOT_STRING_FORMAT_STRING = "Expected %s to be string."
private const val FIELD_NOT_ARRAY_FORMAT_STRING = "Expected %s to be array."
+ private const val COMMENTS_NAME = "comments"
+ private const val EXCLUDE_NAME = "exclude"
+ private const val FRAGMENT_NAME = "#"
+ private const val QUERY_NAME = "?"
+ private const val PATH_NAME = "/"
/**
* Parses a JSON array of statements.
@@ -99,9 +104,7 @@ object StatementParser {
FIELD_NOT_ARRAY_FORMAT_STRING.format(StatementUtils.ASSET_DESCRIPTOR_FIELD_RELATION)
)
val target = AssetFactory.create(targetObject)
- val dynamicAppLinkComponents = parseDynamicAppLinkComponents(
- statement.optJSONObject(StatementUtils.ASSET_DESCRIPTOR_FIELD_RELATION_EXTENSIONS)
- )
+ val dynamicAppLinkComponents = parseDynamicAppLinkComponents(statement)
val statements = (0 until relations.length())
.map { relations.getString(it) }
@@ -129,13 +132,13 @@ object StatementParser {
}
private fun parseComponent(component: JSONObject): DynamicAppLinkComponent {
- val query = component.optJSONObject("?")
+ val query = component.optJSONObject(QUERY_NAME)
return DynamicAppLinkComponent.create(
- component.optBoolean("exclude", false),
- component.optString("#"),
- component.optString("/"),
+ component.optBoolean(EXCLUDE_NAME, false),
+ if (component.has(FRAGMENT_NAME)) component.getString(FRAGMENT_NAME) else null,
+ if (component.has(PATH_NAME)) component.getString(PATH_NAME) else null,
query?.keys()?.asSequence()?.associateWith { query.getString(it) },
- component.optString("comments")
+ component.optString(COMMENTS_NAME)
)
}
diff --git a/packages/StatementService/src/com/android/statementservice/retriever/DynamicAppLinkComponent.java b/packages/StatementService/src/com/android/statementservice/retriever/DynamicAppLinkComponent.java
index dc27e125e204..c32f1949fed4 100644
--- a/packages/StatementService/src/com/android/statementservice/retriever/DynamicAppLinkComponent.java
+++ b/packages/StatementService/src/com/android/statementservice/retriever/DynamicAppLinkComponent.java
@@ -130,7 +130,7 @@ public final class DynamicAppLinkComponent {
@Override
public String toString() {
StringBuilder statement = new StringBuilder();
- statement.append("HandleAllUriRule: ");
+ statement.append("DynamicAppLinkComponent: ");
statement.append(mExclude);
statement.append(", ");
statement.append(mFragment);
diff --git a/packages/StatementService/src/com/android/statementservice/retriever/JsonParser.java b/packages/StatementService/src/com/android/statementservice/retriever/JsonParser.java
index 7635e8234dc0..ab1853c1d3ae 100644
--- a/packages/StatementService/src/com/android/statementservice/retriever/JsonParser.java
+++ b/packages/StatementService/src/com/android/statementservice/retriever/JsonParser.java
@@ -24,8 +24,6 @@ import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
/**
* A helper class that creates a {@link JSONObject} from a {@link JsonReader}.
@@ -48,7 +46,7 @@ public final class JsonParser {
JsonToken token = reader.peek();
if (token.equals(JsonToken.BEGIN_ARRAY)) {
- output.put(fieldName, new JSONArray(parseArray(reader)));
+ output.put(fieldName, parseArray(reader));
} else if (token.equals(JsonToken.STRING)) {
output.put(fieldName, reader.nextString());
} else if (token.equals(JsonToken.BEGIN_OBJECT)) {
@@ -57,9 +55,11 @@ public final class JsonParser {
} catch (JSONException e) {
errorMsg = e.getMessage();
}
+ } else if (token.equals(JsonToken.BOOLEAN)) {
+ output.put(fieldName, reader.nextBoolean());
} else {
reader.skipValue();
- errorMsg = "Unsupported value type.";
+ errorMsg = "Unsupported value type: " + token;
}
}
reader.endObject();
@@ -72,17 +72,36 @@ public final class JsonParser {
}
/**
- * Parses one string array from the {@link JsonReader}.
+ * Parses one JSON array from the {@link JsonReader}.
*/
- public static List<String> parseArray(JsonReader reader) throws IOException {
- ArrayList<String> output = new ArrayList<>();
+ public static JSONArray parseArray(JsonReader reader) throws IOException, JSONException {
+ JSONArray output = new JSONArray();
+ String errorMsg = null;
reader.beginArray();
while (reader.hasNext()) {
- output.add(reader.nextString());
+ JsonToken token = reader.peek();
+ if (token.equals(JsonToken.BEGIN_ARRAY)) {
+ output.put(parseArray(reader));
+ } else if (token.equals(JsonToken.STRING)) {
+ output.put(reader.nextString());
+ } else if (token.equals(JsonToken.BEGIN_OBJECT)) {
+ try {
+ output.put(parse(reader));
+ } catch (JSONException e) {
+ errorMsg = e.getMessage();
+ }
+ } else {
+ reader.skipValue();
+ errorMsg = "Unsupported value type: " + token;
+ }
}
reader.endArray();
+ if (errorMsg != null) {
+ throw new JSONException(errorMsg);
+ }
+
return output;
}
}
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index d1a22e8612d3..3d250fd82473 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -81,33 +81,37 @@ filegroup {
visibility: ["//visibility:private"],
}
-// Tests where robolectric conversion caused errors in SystemUITests at runtime
+// Tests where robolectric failed at runtime. (go/central-multivalent)
filegroup {
- name: "SystemUI-tests-broken-robofiles-sysui-run",
+ name: "SystemUI-tests-broken-robofiles-run",
srcs: [
+ "tests/src/**/systemui/shade/NotificationShadeWindowViewControllerTest.kt",
+ "tests/src/**/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt",
+ "tests/src/**/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModelParameterizedTest.kt",
+ "tests/src/**/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt",
+ "tests/src/**/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt",
+ "tests/src/**/systemui/animation/back/FlingOnBackAnimationCallbackTest.kt",
+ "tests/src/**/systemui/education/domain/ui/view/ContextualEduDialogTest.kt",
+ "tests/src/**/systemui/screenshot/ActionIntentCreatorTest.kt",
+ "tests/src/**/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt",
+ "tests/src/**/systemui/accessibility/WindowMagnificationControllerTest.java",
"tests/src/**/systemui/broadcast/BroadcastDispatcherTest.kt",
- "tests/src/**/systemui/broadcast/ActionReceiverTest.kt",
"tests/src/**/systemui/globalactions/GlobalActionsDialogLiteTest.java",
"tests/src/**/systemui/globalactions/GlobalActionsImeTest.java",
- "tests/src/**/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt",
- "tests/src/**/systemui/media/dialog/MediaOutputAdapterTest.java",
"tests/src/**/systemui/media/dialog/MediaOutputBaseDialogTest.java",
"tests/src/**/systemui/media/dialog/MediaOutputBroadcastDialogTest.java",
"tests/src/**/systemui/media/dialog/MediaOutputDialogTest.java",
- "tests/src/**/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt",
"tests/src/**/systemui/settings/brightness/BrightnessDialogTest.kt",
- ],
-}
-
-// Tests where robolectric failed at runtime. (go/central-multivalent)
-filegroup {
- name: "SystemUI-tests-broken-robofiles-run",
- srcs: [
- "tests/src/**/systemui/ExpandHelperTest.java",
+ "tests/src/**/systemui/shared/clocks/view/SimpleDigitalClockTextViewTest.kt",
+ "tests/src/**/systemui/statusbar/policy/SecurityControllerTest.java",
+ "tests/src/**/systemui/lifecycle/SysUiViewModelTest.kt",
+ "tests/src/**/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt",
+ "tests/src/**/systemui/graphics/ImageLoaderContentProviderTest.kt",
+ "tests/src/**/systemui/flags/FakeFeatureFlagsTest.kt",
+ "tests/src/**/systemui/communal/data/backup/CommunalBackupUtilsTest.kt",
"tests/src/**/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java",
"tests/src/**/systemui/accessibility/AccessibilityGestureTargetsObserverTest.java",
"tests/src/**/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java",
- "tests/src/**/systemui/accessibility/floatingmenu/AccessibilityTargetAdapterTest.java",
"tests/src/**/systemui/screenshot/appclips/AppClipsActivityTest.java",
"tests/src/**/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java",
"tests/src/**/systemui/screenshot/appclips/AppClipsViewModelTest.java",
@@ -124,36 +128,27 @@ filegroup {
"tests/src/**/systemui/classifier/FalsingDataProviderTest.java",
"tests/src/**/systemui/screenshot/ImageExporterTest.java",
"tests/src/**/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt",
- "tests/src/**/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractorTest.kt",
"tests/src/**/systemui/logcat/LogAccessDialogActivityTest.java",
"tests/src/**/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt",
"tests/src/**/systemui/media/controls/domain/pipeline/MediaSessionBasedFilterTest.kt",
"tests/src/**/systemui/accessibility/floatingmenu/MenuNotificationFactoryTest.java",
"tests/src/**/systemui/accessibility/floatingmenu/MenuViewLayerTest.java",
- "tests/src/**/systemui/accessibility/floatingmenu/MenuViewTest.java",
"tests/src/**/systemui/classifier/PointerCountClassifierTest.java",
"tests/src/**/systemui/accessibility/floatingmenu/RadiiAnimatorTest.java",
"tests/src/**/systemui/screenrecord/RecordingControllerTest.java",
"tests/src/**/systemui/screenshot/RequestProcessorTest.kt",
"tests/src/**/systemui/media/controls/domain/resume/ResumeMediaBrowserTest.kt",
- "tests/src/**/systemui/screenshot/SaveImageInBackgroundTaskTest.kt",
"tests/src/**/systemui/screenshot/scroll/ScrollCaptureClientTest.java",
"tests/src/**/systemui/accessibility/SecureSettingsContentObserverTest.java",
"tests/src/**/systemui/media/controls/ui/viewmodel/SeekBarViewModelTest.kt",
"tests/src/**/systemui/qs/external/TileServicesTest.java",
"tests/src/**/systemui/ambient/touch/TouchMonitorTest.java",
- "tests/src/**/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java",
"tests/src/**/systemui/accessibility/WindowMagnificationSettingsTest.java",
- "tests/src/androidx/core/animation/AnimatorTestRuleIsolationTest.kt",
"tests/src/**/systemui/CameraProtectionLoaderImplTest.kt",
- "tests/src/**/systemui/DependencyTest.java",
- "tests/src/**/systemui/InitControllerTest.java",
"tests/src/**/systemui/SliceBroadcastRelayHandlerTest.java",
"tests/src/**/systemui/SystemUIApplicationTest.kt",
"tests/src/**/systemui/SysUICutoutProviderTest.kt",
- "tests/src/**/keyguard/ActiveUnlockConfigTest.kt",
"tests/src/**/keyguard/AdminSecondaryLockScreenControllerTest.java",
- "tests/src/**/keyguard/KeyguardClockAccessibilityDelegateTest.java",
"tests/src/**/keyguard/KeyguardStatusViewControllerTest.java",
"tests/src/**/systemui/accessibility/AccessibilityButtonModeObserverTest.java",
"tests/src/**/systemui/accessibility/AccessibilityButtonTargetsObserverTest.java",
@@ -164,16 +159,12 @@ filegroup {
"tests/src/**/systemui/animation/TextAnimatorTest.kt",
"tests/src/**/systemui/animation/TextInterpolatorTest.kt",
"tests/src/**/systemui/animation/ActivityTransitionAnimatorTest.kt",
- "tests/src/**/systemui/animation/AnimatorTestRuleOrderTest.kt",
"tests/src/**/systemui/animation/DialogTransitionAnimatorTest.kt",
- "tests/src/**/systemui/broadcast/ActionReceiverTest.kt",
"tests/src/**/systemui/broadcast/BroadcastDispatcherTest.kt",
- "tests/src/**/systemui/compose/ComposeInitializerTest.kt",
"tests/src/**/systemui/controls/ui/ControlsActivityTest.kt",
"tests/src/**/systemui/controls/management/ControlsEditingActivityTest.kt",
"tests/src/**/systemui/controls/management/ControlsRequestDialogTest.kt",
"tests/src/**/systemui/controls/ui/DetailDialogTest.kt",
- "tests/src/**/systemui/fontscaling/FontScalingDialogDelegateTest.kt",
"tests/src/**/systemui/keyguard/CustomizationProviderTest.kt",
"tests/src/**/systemui/globalactions/GlobalActionsColumnLayoutTest.java",
"tests/src/**/systemui/globalactions/GlobalActionsDialogLiteTest.java",
@@ -182,10 +173,6 @@ filegroup {
"tests/src/**/systemui/keyguard/CustomizationProviderTest.kt",
"tests/src/**/systemui/keyguard/KeyguardViewMediatorTest.java",
"tests/src/**/systemui/keyguard/LifecycleTest.java",
- "tests/src/**/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt",
- "tests/src/**/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt",
- "tests/src/**/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryTest.kt",
- "tests/src/**/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModelTest.kt",
"tests/src/**/systemui/lifecycle/RepeatWhenAttachedTest.kt",
"tests/src/**/systemui/log/LogBufferTest.kt",
"tests/src/**/systemui/media/dialog/MediaOutputBaseDialogTest.java",
@@ -193,52 +180,35 @@ filegroup {
"tests/src/**/systemui/media/dialog/MediaOutputDialogTest.java",
"tests/src/**/systemui/media/controls/domain/resume/MediaResumeListenerTest.kt",
"tests/src/**/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt",
- "tests/src/**/systemui/navigationbar/views/NavigationBarButtonTest.java",
"tests/src/**/systemui/people/PeopleProviderTest.java",
"tests/src/**/systemui/people/PeopleSpaceUtilsTest.java",
"tests/src/**/systemui/people/widget/PeopleSpaceWidgetManagerTest.java",
"tests/src/**/systemui/people/PeopleTileViewHelperTest.java",
"tests/src/**/systemui/power/data/repository/PowerRepositoryImplTest.kt",
- "tests/src/**/systemui/privacy/PrivacyConfigFlagsTest.kt",
- "tests/src/**/systemui/privacy/PrivacyDialogV2Test.kt",
- "tests/src/**/systemui/qs/external/TileRequestDialogEventLoggerTest.kt",
- "tests/src/**/systemui/qs/AutoAddTrackerTest.kt",
- "tests/src/**/systemui/qs/external/TileRequestDialogEventLoggerTest.kt",
"tests/src/**/systemui/qs/tiles/DndTileTest.kt",
"tests/src/**/systemui/qs/tiles/DreamTileTest.java",
- "tests/src/**/systemui/qs/FgsManagerControllerTest.java",
"tests/src/**/systemui/qs/QSPanelTest.kt",
"tests/src/**/systemui/reardisplay/RearDisplayCoreStartableTest.kt",
"tests/src/**/systemui/reardisplay/RearDisplayDialogControllerTest.java",
"tests/src/**/systemui/reardisplay/RearDisplayInnerDialogDelegateTest.kt",
"tests/src/**/systemui/statusbar/KeyboardShortcutListSearchTest.java",
"tests/src/**/systemui/statusbar/KeyboardShortcutsTest.java",
- "tests/src/**/systemui/statusbar/KeyguardIndicationControllerWithCoroutinesTest.kt",
- "tests/src/**/systemui/statusbar/notification/AssistantFeedbackControllerTest.java",
"tests/src/**/systemui/statusbar/notification/collection/NotificationEntryTest.java",
- "tests/src/**/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt",
"tests/src/**/systemui/statusbar/notification/collection/ShadeListBuilderTest.java",
- "tests/src/**/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java",
"tests/src/**/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java",
"tests/src/**/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt",
"tests/src/**/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt",
- "tests/src/**/systemui/statusbar/NotificationLockscreenUserManagerTest.java",
"tests/src/**/systemui/statusbar/notification/logging/NotificationLoggerTest.java",
"tests/src/**/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java",
- "tests/src/**/systemui/statusbar/notification/row/NotificationContentInflaterTest.java",
"tests/src/**/systemui/statusbar/notification/row/NotificationContentViewTest.kt",
"tests/src/**/systemui/statusbar/notification/row/NotificationConversationInfoTest.java",
- "tests/src/**/systemui/statusbar/notification/row/NotificationGutsManagerTest.java",
"tests/src/**/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt",
"tests/src/**/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapperTest.kt",
"tests/src/**/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java",
- "tests/src/**/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt",
- "tests/src/**/systemui/statusbar/phone/AutoTileManagerTest.java",
"tests/src/**/systemui/statusbar/phone/CentralSurfacesImplTest.java",
"tests/src/**/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java",
"tests/src/**/systemui/statusbar/phone/PhoneStatusBarTransitionsTest.kt",
"tests/src/**/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt",
- "tests/src/**/systemui/statusbar/phone/PhoneStatusBarView.java",
"tests/src/**/systemui/statusbar/phone/PhoneStatusBarViewTest.kt",
"tests/src/**/systemui/statusbar/phone/StatusBarBoundsProviderTest.kt",
"tests/src/**/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationControllerTest.kt",
@@ -252,13 +222,9 @@ filegroup {
"tests/src/**/systemui/statusbar/policy/LocationControllerImplTest.java",
"tests/src/**/systemui/statusbar/policy/RemoteInputViewTest.java",
"tests/src/**/systemui/statusbar/policy/SmartReplyViewTest.java",
- "tests/src/**/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt",
- "tests/src/**/systemui/statusbar/StatusBarStateControllerImplTest.kt",
"tests/src/**/systemui/theme/ThemeOverlayApplierTest.java",
"tests/src/**/systemui/touch/TouchInsetManagerTest.java",
"tests/src/**/systemui/util/LifecycleFragmentTest.java",
- "tests/src/**/systemui/util/kotlin/PairwiseFlowTest",
- "tests/src/**/systemui/util/sensors/AsyncManagerTest.java",
"tests/src/**/systemui/util/sensors/ThresholdSensorImplTest.java",
"tests/src/**/systemui/volume/VolumeDialogImplTest.java",
"tests/src/**/systemui/wallet/controller/QuickAccessWalletControllerTest.java",
@@ -271,26 +237,17 @@ filegroup {
"tests/src/**/systemui/clipboardoverlay/ClipboardListenerTest.java",
"tests/src/**/systemui/communal/data/db/CommunalDatabaseMigrationsTest.kt",
"tests/src/**/systemui/communal/data/db/CommunalWidgetDaoTest.kt",
- "tests/src/**/systemui/display/data/repository/DisplayWindowPropertiesRepositoryImplTest.kt",
"tests/src/**/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt",
"tests/src/**/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt",
"tests/src/**/systemui/lifecycle/ActivatableTest.kt",
- "tests/src/**/systemui/lifecycle/HydratorTest.kt",
"tests/src/**/systemui/media/dialog/MediaSwitchingControllerTest.java",
"tests/src/**/systemui/qs/QSImplTest.java",
"tests/src/**/systemui/qs/panels/ui/compose/DragAndDropTest.kt",
"tests/src/**/systemui/qs/panels/ui/compose/ResizingTest.kt",
"tests/src/**/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java",
- "tests/src/**/systemui/accessibility/floatingmenu/PositionTest.java",
"tests/src/**/systemui/animation/TransitionAnimatorTest.kt",
- "tests/src/**/systemui/screenshot/scroll/ScrollCaptureControllerTest.java",
- "tests/src/**/systemui/lifecycle/SysuiViewModelTest.kt",
- "tests/src/**/systemui/flags/FakeFeatureFlags.kt",
"tests/src/**/systemui/animation/TransitionAnimatorTest.kt",
- "tests/src/**/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java",
"tests/src/**/systemui/statusbar/connectivity/NetworkControllerSignalTest.java",
- "tests/src/**/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt",
- "tests/src/**/systemui/statusbar/policy/RotationLockControllerImplTest.java",
"tests/src/**/systemui/statusbar/phone/ScrimControllerTest.java",
"tests/src/**/systemui/toast/ToastUITest.java",
"tests/src/**/systemui/statusbar/policy/FlashlightControllerImplTest.kt",
@@ -329,47 +286,48 @@ filegroup {
"tests/src/**/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java",
"tests/src/**/systemui/qs/external/TileLifecycleManagerTest.java",
"tests/src/**/systemui/ScreenDecorationsTest.java",
+ "tests/src/**/systemui/statusbar/policy/BatteryControllerStartableTest.java",
"tests/src/**/keyguard/CarrierTextManagerTest.java",
"tests/src/**/keyguard/KeyguardUpdateMonitorTest.java",
],
}
-// Tests where robolectric failed at compile time. (go/multivalent-tests)
+// Tests where compilation failed due to kotlin internal references.
filegroup {
- name: "SystemUI-tests-broken-robofiles-compile",
+ name: "SystemUI-tests-broken-robofiles-internal",
srcs: [
- "tests/src/**/systemui/statusbar/notification/icon/IconManagerTest.kt",
- "tests/src/**/systemui/statusbar/KeyguardIndicationControllerTest.java",
- "tests/src/**/systemui/doze/DozeScreenStateTest.java",
- "tests/src/**/systemui/notetask/NoteTaskInitializerTest.kt",
- "tests/src/**/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt",
- "tests/src/**/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt",
- "tests/src/**/systemui/controls/management/ControlsFavoritingActivityTest.kt",
- "tests/src/**/systemui/controls/management/ControlsProviderSelectorActivityTest.kt",
- "tests/src/**/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt",
- "tests/src/**/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt",
- "tests/src/**/systemui/qs/tileimpl/QSTileViewImplTest.kt",
+ "tests/src/**/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt",
+ "tests/src/**/android/systemui/statusbar/SplitShadeLockScreenOverScrollerTest.kt",
+ "tests/src/**/android/systemui/statusbar/notification/icon/IconManagerTest.kt",
+ "tests/src/**/android/systemui/notetask/NoteTaskInitializerTest.kt",
+ "tests/src/**/systemui/statusbar/policy/VariableDateViewControllerTest.kt",
+ "tests/src/**/systemui/statusbar/policy/DeviceControlsControllerImplTest.kt",
+ "tests/src/**/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt",
+ "tests/src/**/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt",
+ "tests/src/**/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt",
+ "tests/src/**/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt",
"tests/src/**/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt",
+ "tests/src/**/systemui/statusbar/policy/WalletControllerImplTest.kt",
"tests/src/**/keyguard/ClockEventControllerTest.kt",
- "tests/src/**/systemui/bluetooth/qsdialog/BluetoothAutoOnRepositoryTest.kt",
+ "tests/src/**/systemui/bluetooth/qsdialog/BluetoothStateInteractorTest.kt",
"tests/src/**/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt",
"tests/src/**/systemui/bluetooth/qsdialog/BluetoothTileDialogRepositoryTest.kt",
"tests/src/**/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt",
+ "tests/src/**/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt",
"tests/src/**/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt",
"tests/src/**/systemui/broadcast/UserBroadcastDispatcherTest.kt",
"tests/src/**/systemui/charging/WiredChargingRippleControllerTest.kt",
"tests/src/**/systemui/clipboardoverlay/ClipboardModelTest.kt",
"tests/src/**/systemui/controls/controller/AuxiliaryPersistenceWrapperTest.kt",
- "tests/src/**/systemui/controls/controller/ControlsBindingControllerImplTest.kt",
"tests/src/**/systemui/controls/controller/ControlsControllerImplTest.kt",
"tests/src/**/systemui/controls/controller/DeletionJobServiceTest.kt",
- "tests/src/**/systemui/controls/settings/ControlsSettingsDialogManagerImplTest.kt",
+ "tests/src/**/systemui/controls/management/ControlsFavoritingActivityTest.kt",
"tests/src/**/systemui/controls/ui/ControlsUiControllerImplTest.kt",
- "tests/src/**/systemui/controls/ui/ControlViewHolderTest.kt",
"tests/src/**/systemui/controls/ui/SelectionItemTest.kt",
"tests/src/**/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt",
"tests/src/**/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImplTest.kt",
- "tests/src/**/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt",
+ "tests/src/**/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt",
+ "tests/src/**/systemui/media/controls/ui/MediaPlayerDataTest.kt",
"tests/src/**/systemui/media/controls/ui/animation/AnimationBindHandlerTest.kt",
"tests/src/**/systemui/media/controls/ui/animation/ColorSchemeTransitionTest.kt",
"tests/src/**/systemui/media/controls/ui/animation/MetadataAnimationHandlerTest.kt",
@@ -377,7 +335,6 @@ filegroup {
"tests/src/**/systemui/media/controls/ui/controller/MediaControlPanelTest.kt",
"tests/src/**/systemui/media/controls/ui/controller/MediaViewControllerTest.kt",
"tests/src/**/systemui/media/controls/ui/drawable/SquigglyProgressTest.kt",
- "tests/src/**/systemui/media/controls/ui/MediaPlayerDataTest.kt",
"tests/src/**/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt",
"tests/src/**/systemui/navigationbar/gestural/BackPanelControllerTest.kt",
"tests/src/**/systemui/notetask/NoteTaskControllerTest.kt",
@@ -386,61 +343,61 @@ filegroup {
"tests/src/**/systemui/qs/external/CustomTileStatePersisterTest.kt",
"tests/src/**/systemui/qs/external/TileRequestDialogTest.kt",
"tests/src/**/systemui/qs/external/TileServiceRequestControllerTest.kt",
- "tests/src/**/systemui/qs/tileimpl/TilesStatesTextTest.kt",
+ "tests/src/**/systemui/qs/tileimpl/QSTileViewImplTest.kt",
"tests/src/**/systemui/qs/tiles/AlarmTileTest.kt",
"tests/src/**/systemui/qs/tiles/BluetoothTileTest.kt",
- "tests/src/**/systemui/screenshot/ScreenshotPolicyImplTest.kt",
- "tests/src/**/systemui/settings/DisplayTrackerImplTest.kt",
+ "tests/src/**/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt",
+ "tests/src/**/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt",
+ "tests/src/**/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt",
+ "tests/src/**/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt",
+ "tests/src/**/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfigTest.kt",
+ "tests/src/**/systemui/statusbar/phone/fragment/MultiSourceMinAlphaControllerTest.kt",
+ "tests/src/**/systemui/statusbar/phone/FoldStateListenerTest.kt",
+ "tests/src/**/systemui/statusbar/notification/stack/ui/view/NotificationStatsLoggerTest.kt",
+ "tests/src/**/systemui/statusbar/notification/row/TextPrecomputerTest.kt",
+ "tests/src/**/systemui/statusbar/notification/row/SingleLineConversationViewBinderTest.kt",
+ "tests/src/**/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt",
+ "tests/src/**/systemui/statusbar/notification/row/SingleLineViewInflaterTest.kt",
+ "tests/src/**/systemui/statusbar/notification/row/ChannelEditorDialogControllerTest.kt",
+ "tests/src/**/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt",
+ "tests/src/**/systemui/statusbar/notification/logging/NotificationMemoryMeterTest.kt",
+ "tests/src/**/systemui/statusbar/notification/logging/NotificationMemoryViewWalkerTest.kt",
+ "tests/src/**/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinatorTest.kt",
+ "tests/src/**/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt",
+ "tests/src/**/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorTest.kt",
+ "tests/src/**/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinatorTest.kt",
+ "tests/src/**/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinatorTest.kt",
+ "tests/src/**/systemui/statusbar/notification/RoundableTest.kt",
+ "tests/src/**/systemui/stylus/StylusUsiPowerUiTest.kt",
+ "tests/src/**/systemui/statusbar/gesture/GenericGestureDetectorTest.kt",
+ "tests/src/**/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt",
+ "tests/src/**/systemui/statusbar/connectivity/MobileStateTest.kt",
+ "tests/src/**/systemui/statusbar/commandline/CommandParserTest.kt",
+ "tests/src/**/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt",
+ "tests/src/**/systemui/statusbar/LightRevealScrimTest.kt",
+ "tests/src/**/systemui/shade/transition/LargeScreenShadeInterpolatorImplTest.kt",
+ "tests/src/**/systemui/shade/ShadeExpansionStateManagerTest.kt",
+ "tests/src/**/systemui/shade/ShadeHeaderControllerTest.kt",
+ "tests/src/**/systemui/shade/NotificationsQSContainerControllerTest.kt",
"tests/src/**/systemui/settings/UserFileManagerImplTest.kt",
"tests/src/**/systemui/settings/UserTrackerImplReceiveTest.kt",
"tests/src/**/systemui/settings/UserTrackerImplTest.kt",
"tests/src/**/systemui/shade/GlanceableHubContainerControllerTest.kt",
"tests/src/**/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt",
- "tests/src/**/systemui/shade/NotificationsQSContainerControllerTest.kt",
- "tests/src/**/systemui/shade/ShadeExpansionStateManagerTest.kt",
- "tests/src/**/systemui/shade/ShadeHeaderControllerTest.kt",
- "tests/src/**/systemui/shade/transition/LargeScreenShadeInterpolatorImplTest.kt",
- "tests/src/**/systemui/statusbar/commandline/CommandParserTest.kt",
- "tests/src/**/systemui/statusbar/connectivity/MobileStateTest.kt",
- "tests/src/**/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt",
- "tests/src/**/systemui/statusbar/gesture/GenericGestureDetectorTest.kt",
- "tests/src/**/systemui/statusbar/LightRevealScrimTest.kt",
- "tests/src/**/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt",
- "tests/src/**/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinatorTest.kt",
- "tests/src/**/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinatorTest.kt",
- "tests/src/**/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorTest.kt",
- "tests/src/**/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt",
- "tests/src/**/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinatorTest.kt",
- "tests/src/**/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt",
- "tests/src/**/systemui/statusbar/notification/logging/NotificationMemoryMeterTest.kt",
- "tests/src/**/systemui/statusbar/notification/logging/NotificationMemoryViewWalkerTest.kt",
- "tests/src/**/systemui/statusbar/notification/RoundableTest.kt",
- "tests/src/**/systemui/statusbar/notification/row/ChannelEditorDialogControllerTest.kt",
- "tests/src/**/systemui/statusbar/notification/row/SingleLineConversationViewBinderTest.kt",
- "tests/src/**/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt",
- "tests/src/**/systemui/statusbar/notification/row/SingleLineViewInflaterTest.kt",
- "tests/src/**/systemui/statusbar/notification/row/TextPrecomputerTest.kt",
- "tests/src/**/systemui/statusbar/phone/FoldStateListenerTest.kt",
- "tests/src/**/systemui/statusbar/phone/fragment/MultiSourceMinAlphaControllerTest.kt",
- "tests/src/**/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfigTest.kt",
- "tests/src/**/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt",
- "tests/src/**/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt",
- "tests/src/**/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt",
- "tests/src/**/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt",
- "tests/src/**/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt",
- "tests/src/**/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt",
- "tests/src/**/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt",
- "tests/src/**/systemui/statusbar/policy/DeviceControlsControllerImplTest.kt",
- "tests/src/**/systemui/statusbar/policy/VariableDateViewControllerTest.kt",
- "tests/src/**/systemui/statusbar/policy/WalletControllerImplTest.kt",
- "tests/src/**/systemui/statusbar/SplitShadeLockScreenOverScrollerTest.kt",
- "tests/src/**/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt",
- "tests/src/**/systemui/statusbar/policy/BatteryControllerStartableTest.java",
- "tests/src/**/systemui/shared/plugins/PluginActionManagerTest.java",
- "tests/src/**/systemui/statusbar/policy/SecurityControllerTest.java",
- "tests/src/**/systemui/shared/clocks/view/SimpleDigitalClockTextViewTest.kt",
+ "tests/src/**/systemui/screenshot/ScreenshotPolicyImplTest.kt",
+ "tests/src/**/systemui/qs/tileimpl/TilesStatesTextTest.kt",
+ "tests/src/**/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt",
+ "tests/src/**/systemui/controls/ui/ControlViewHolderTest.kt",
+ "tests/src/**/systemui/controls/settings/ControlsSettingsDialogManagerImplTest.kt",
+ "tests/src/**/systemui/controls/controller/ControlsBindingControllerImplTest.kt",
+ "tests/src/**/systemui/bluetooth/qsdialog/BluetoothAutoOnRepositoryTest.kt",
+ "tests/src/**/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt",
+ "tests/src/**/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt",
+ "tests/src/**/systemui/controls/management/ControlsProviderSelectorActivityTest.kt",
+ "tests/src/**/systemui/settings/DisplayTrackerImplTest.kt",
+ "tests/src/**/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt",
+ "tests/src/**/systemui/wmshell/BubblesTest.java",
],
- visibility: ["//visibility:private"],
}
//Create a library to expose SystemUI's resources to other modules.
@@ -903,9 +860,8 @@ android_robolectric_test {
],
exclude_srcs: [
":SystemUI-tests-broken-robofiles-mockito-extended",
- ":SystemUI-tests-broken-robofiles-compile",
+ ":SystemUI-tests-broken-robofiles-internal",
":SystemUI-tests-broken-robofiles-run",
- ":SystemUI-tests-broken-robofiles-sysui-run",
],
static_libs: [
"RoboTestLibraries",
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java
index c71ef8360008..129dd9b3c14d 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java
@@ -36,7 +36,6 @@ import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceManager;
-import com.android.systemui.accessibility.accessibilitymenu.Flags;
import com.android.systemui.accessibility.accessibilitymenu.R;
/**
@@ -62,10 +61,8 @@ public class A11yMenuSettingsActivity extends FragmentActivity {
((TextView) findViewById(R.id.action_bar_title)).setText(
getResources().getString(R.string.accessibility_menu_settings_name)
);
- if (Flags.actionBarWrapContent()) {
- setHeightWrapContent(findViewById(com.android.internal.R.id.action_bar));
- setHeightWrapContent(findViewById(com.android.internal.R.id.action_bar_container));
- }
+ setHeightWrapContent(findViewById(com.android.internal.R.id.action_bar));
+ setHeightWrapContent(findViewById(com.android.internal.R.id.action_bar_container));
}
private void setHeightWrapContent(View view) {
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java
index 3db61a58c7a3..6bc0f42f39aa 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java
@@ -220,9 +220,6 @@ public class A11yMenuOverlayLayout {
@SuppressLint("MissingPermission")
private boolean isShortcutRestricted(int shortcutId) {
- if (!Flags.hideRestrictedActions()) {
- return false;
- }
final UserManager userManager = mService.getSystemService(UserManager.class);
if (userManager == null) {
return false;
@@ -366,12 +363,11 @@ public class A11yMenuOverlayLayout {
if (mLayout.getVisibility() == View.VISIBLE) {
mLayout.setVisibility(View.GONE);
} else {
- if (Flags.hideRestrictedActions()) {
- // Reconfigure the shortcut list in case the set of restricted actions has changed.
- mA11yMenuViewPager.configureViewPagerAndFooter(
- mLayout, createShortcutList(), getPageIndex());
- updateViewLayout();
- }
+ // Reconfigure the shortcut list in case the set of restricted actions has changed.
+ mA11yMenuViewPager.configureViewPagerAndFooter(
+ mLayout, createShortcutList(), getPageIndex());
+ updateViewLayout();
+
mLayout.setVisibility(View.VISIBLE);
}
}
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
index 4ab771be55f4..71726199aeb6 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
@@ -46,9 +46,6 @@ import android.hardware.display.DisplayManager;
import android.media.AudioManager;
import android.os.PowerManager;
import android.os.UserManager;
-import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.platform.uiautomator_helpers.WaitUtils;
import android.provider.Settings;
import android.util.Log;
@@ -63,7 +60,6 @@ import androidx.test.uiautomator.Configurator;
import androidx.test.uiautomator.UiDevice;
import com.android.compatibility.common.util.TestUtils;
-import com.android.systemui.accessibility.accessibilitymenu.Flags;
import com.android.systemui.accessibility.accessibilitymenu.model.A11yMenuShortcut.ShortcutId;
import org.junit.After;
@@ -71,7 +67,6 @@ import org.junit.AfterClass;
import org.junit.Assume;
import org.junit.Before;
import org.junit.BeforeClass;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -82,9 +77,6 @@ import java.util.concurrent.atomic.AtomicInteger;
@RunWith(AndroidJUnit4.class)
public class AccessibilityMenuServiceTest {
- @Rule
- public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
-
private static final String TAG = "A11yMenuServiceTest";
private static final int CLICK_ID = AccessibilityNodeInfo.ACTION_CLICK;
@@ -499,7 +491,6 @@ public class AccessibilityMenuServiceTest {
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_HIDE_RESTRICTED_ACTIONS)
public void testRestrictedActions_BrightnessNotAvailable() throws Throwable {
try {
setUserRestriction(UserManager.DISALLOW_CONFIG_BRIGHTNESS, true);
@@ -519,7 +510,6 @@ public class AccessibilityMenuServiceTest {
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_HIDE_RESTRICTED_ACTIONS)
public void testRestrictedActions_VolumeNotAvailable() throws Throwable {
try {
setUserRestriction(UserManager.DISALLOW_ADJUST_VOLUME, true);
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index e59fe47194da..20b83e10e821 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1509,6 +1509,16 @@ flag {
}
flag {
+ name: "sim_pin_use_slot_id"
+ namespace: "systemui"
+ description: "Reorient SIM data processing around slotId instead of subId"
+ bug: "376173142"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "use_transitions_for_keyguard_occluded"
namespace: "systemui"
description: "Use Keyguard Transitions to set Notification Shade occlusion state"
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 5b1203fa17c9..787edfb9168c 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
@@ -141,6 +141,7 @@ import androidx.compose.ui.semantics.CustomAccessibilityAction
import androidx.compose.ui.semantics.clearAndSetSemantics
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.customActions
+import androidx.compose.ui.semantics.heading
import androidx.compose.ui.semantics.onClick
import androidx.compose.ui.semantics.paneTitle
import androidx.compose.ui.semantics.semantics
@@ -902,11 +903,17 @@ private fun EmptyStateCta(contentPadding: PaddingValues, viewModel: BaseCommunal
Arrangement.spacedBy(Dimensions.Spacing, Alignment.CenterVertically),
horizontalAlignment = Alignment.CenterHorizontally,
) {
+ val titleForEmptyStateCTA = stringResource(R.string.title_for_empty_state_cta)
Text(
- text = stringResource(R.string.title_for_empty_state_cta),
+ text = titleForEmptyStateCTA,
style = MaterialTheme.typography.displaySmall,
textAlign = TextAlign.Center,
color = colors.secondary,
+ modifier =
+ Modifier.focusable().semantics(mergeDescendants = true) {
+ contentDescription = titleForEmptyStateCTA
+ heading()
+ },
)
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) {
Button(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalTouchableSurface.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalTouchableSurface.kt
index 3707a87249b9..f2edec657cd4 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalTouchableSurface.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalTouchableSurface.kt
@@ -27,6 +27,8 @@ import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.key.onPreviewKeyEvent
import androidx.compose.ui.input.pointer.motionEventSpy
+import androidx.compose.ui.semantics.hideFromAccessibility
+import androidx.compose.ui.semantics.semantics
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
@OptIn(ExperimentalFoundationApi::class, ExperimentalComposeUiApi::class)
@@ -42,6 +44,9 @@ fun CommunalTouchableSurface(
Box(
modifier =
modifier
+ // The touchable surface is hidden for accessibility because these actions are
+ // already provided through custom accessibility actions.
+ .semantics { hideFromAccessibility() }
.combinedClickable(
onLongClick = viewModel::onLongClick,
onClick = viewModel::onClick,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
index ef5e90bd7aad..7a500805809d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
@@ -94,7 +94,7 @@ internal constructor(
private val scope: CoroutineScope,
private val updateDragPositionForRemove: (draggingBoundingBox: IntRect) -> Boolean,
) {
- var draggingItemKey by mutableStateOf<Any?>(null)
+ var draggingItemKey by mutableStateOf<String?>(null)
private set
var isDraggingToRemove by mutableStateOf(false)
@@ -138,7 +138,7 @@ internal constructor(
// before content padding from the initial pointer position
.firstItemAtOffset(normalizedOffset - contentOffset)
?.apply {
- draggingItemKey = key
+ draggingItemKey = key as String
draggingItemInitialOffset = this.offset.toOffset()
return true
}
@@ -284,7 +284,9 @@ fun Modifier.dragContainer(
contentOffset,
)
) {
- viewModel.onReorderWidgetStart()
+ // draggingItemKey is guaranteed to be non-null here because it is set in
+ // onDragStart()
+ viewModel.onReorderWidgetStart(dragDropState.draggingItemKey!!)
}
},
onDragEnd = {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt
index f84865ffc6af..3fce89054b20 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt
@@ -44,8 +44,11 @@ import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.notifications.ui.composable.SnoozeableHeadsUpNotificationSpace
+import com.android.systemui.plugins.qs.TileDetailsViewModel
import com.android.systemui.qs.composefragment.ui.GridAnchor
+import com.android.systemui.qs.flags.QsDetailedView
import com.android.systemui.qs.panels.ui.compose.EditMode
+import com.android.systemui.qs.panels.ui.compose.TileDetails
import com.android.systemui.qs.panels.ui.compose.TileGrid
import com.android.systemui.qs.ui.viewmodel.QuickSettingsContainerViewModel
import com.android.systemui.qs.ui.viewmodel.QuickSettingsShadeOverlayActionsViewModel
@@ -116,24 +119,50 @@ constructor(
}
}
+// A sealed interface to represent the possible states of the `ShadeBody`
+sealed interface ShadeBodyState {
+ data object Editing : ShadeBodyState
+ data object TileDetails : ShadeBodyState
+ data object Default : ShadeBodyState
+}
+
+// Function to map the current state of the `ShadeBody`
+fun checkQsState(isEditing: Boolean, tileDetails: TileDetailsViewModel?): ShadeBodyState {
+ if (isEditing) {
+ return ShadeBodyState.Editing
+ } else if (tileDetails != null && QsDetailedView.isEnabled) {
+ return ShadeBodyState.TileDetails
+ }
+ return ShadeBodyState.Default
+}
+
@Composable
fun SceneScope.ShadeBody(viewModel: QuickSettingsContainerViewModel) {
val isEditing by viewModel.editModeViewModel.isEditing.collectAsStateWithLifecycle()
+ val tileDetails = viewModel.detailsViewModel.activeTileDetails
AnimatedContent(
- targetState = isEditing,
+ targetState = checkQsState(isEditing, tileDetails),
transitionSpec = { fadeIn(tween(500)) togetherWith fadeOut(tween(500)) },
- ) { editing ->
- if (editing) {
- EditMode(
- viewModel = viewModel.editModeViewModel,
- modifier = Modifier.fillMaxWidth().padding(QuickSettingsShade.Dimensions.Padding),
- )
- } else {
- QuickSettingsLayout(
- viewModel = viewModel,
- modifier = Modifier.sysuiResTag("quick_settings_panel"),
- )
+ ) { state ->
+ when (state) {
+ ShadeBodyState.Editing -> {
+ EditMode(
+ viewModel = viewModel.editModeViewModel,
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(QuickSettingsShade.Dimensions.Padding),
+ )
+ }
+ ShadeBodyState.TileDetails -> {
+ TileDetails(viewModel.detailsViewModel)
+ }
+ else -> {
+ QuickSettingsLayout(
+ viewModel = viewModel,
+ modifier = Modifier.sysuiResTag("quick_settings_panel"),
+ )
+ }
}
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index fd1632ed4cb4..22b6dbcf41ec 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -270,6 +270,7 @@ private fun SceneScope.SingleShade(
)
val isEmptySpaceClickable by viewModel.isEmptySpaceClickable.collectAsStateWithLifecycle()
val isMediaVisible by viewModel.isMediaVisible.collectAsStateWithLifecycle()
+ val isQsEnabled by viewModel.isQsEnabled.collectAsStateWithLifecycle()
val shouldPunchHoleBehindScrim =
layoutState.isTransitioningBetween(Scenes.Gone, Scenes.Shade) ||
@@ -372,6 +373,7 @@ private fun SceneScope.SingleShade(
Modifier.padding(bottom = qqsLayoutPaddingBottom)
},
usingCollapsedLandscapeMedia = usingCollapsedLandscapeMedia,
+ isQsEnabled = isQsEnabled,
isInSplitShade = false,
)
@@ -427,6 +429,7 @@ private fun SceneScope.SplitShade(
val screenCornerRadius = LocalScreenCornerRadius.current
val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsStateWithLifecycle()
+ val isQsEnabled by viewModel.isQsEnabled.collectAsStateWithLifecycle()
val isCustomizerShowing by
viewModel.qsSceneAdapter.isCustomizerShowing.collectAsStateWithLifecycle()
val customizingAnimationDuration by
@@ -582,6 +585,7 @@ private fun SceneScope.SplitShade(
dimensionResource(id = R.dimen.qs_horizontal_margin)
),
carouselController = mediaCarouselController,
+ isQsEnabled = isQsEnabled,
isInSplitShade = true,
)
}
@@ -635,10 +639,14 @@ private fun SceneScope.ShadeMediaCarousel(
mediaHost: MediaHost,
carouselController: MediaCarouselController,
mediaOffsetProvider: ShadeMediaOffsetProvider,
+ isInSplitShade: Boolean,
+ isQsEnabled: Boolean,
modifier: Modifier = Modifier,
usingCollapsedLandscapeMedia: Boolean = false,
- isInSplitShade: Boolean,
) {
+ if (!isQsEnabled) {
+ return
+ }
MediaCarousel(
modifier = modifier.fillMaxWidth(),
isVisible = isVisible,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
index aa95abb3528f..aa95abb3528f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefsTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefsTest.java
index 944066fa4954..d47ec8cfe4e5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefsTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefsTest.java
@@ -25,15 +25,12 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
-import android.platform.test.annotations.DisableFlags;
-import android.platform.test.annotations.EnableFlags;
import android.testing.TestableLooper;
import android.util.Size;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.util.FakeSharedPreferences;
@@ -59,18 +56,6 @@ public class WindowMagnificationFrameSizePrefsTest extends SysuiTestCase {
mWindowMagnificationFrameSizePrefs = new WindowMagnificationFrameSizePrefs(mContext);
}
- @DisableFlags(Flags.FLAG_SAVE_AND_RESTORE_MAGNIFICATION_SETTINGS_BUTTONS)
- @Test
- public void saveSizeForCurrentDensity_getExpectedSize() {
- Size testSize = new Size(500, 500);
- mWindowMagnificationFrameSizePrefs
- .saveIndexAndSizeForCurrentDensity(MagnificationSize.CUSTOM, testSize);
-
- assertThat(mWindowMagnificationFrameSizePrefs.getSizeForCurrentDensity())
- .isEqualTo(testSize);
- }
-
- @EnableFlags(Flags.FLAG_SAVE_AND_RESTORE_MAGNIFICATION_SETTINGS_BUTTONS)
@Test
public void saveSizeForCurrentDensity_validPreference_getExpectedSize() {
int testIndex = MagnificationSize.MEDIUM;
@@ -81,7 +66,6 @@ public class WindowMagnificationFrameSizePrefsTest extends SysuiTestCase {
.isEqualTo(testSize);
}
- @EnableFlags(Flags.FLAG_SAVE_AND_RESTORE_MAGNIFICATION_SETTINGS_BUTTONS)
@Test
public void saveSizeForCurrentDensity_validPreference_getExpectedIndex() {
int testIndex = MagnificationSize.MEDIUM;
@@ -92,7 +76,6 @@ public class WindowMagnificationFrameSizePrefsTest extends SysuiTestCase {
.isEqualTo(testIndex);
}
- @EnableFlags(Flags.FLAG_SAVE_AND_RESTORE_MAGNIFICATION_SETTINGS_BUTTONS)
@Test
public void saveSizeForCurrentDensity_invalidPreference_getDefaultIndex() {
mSharedPreferences
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/activity/data/repository/ActivityManagerRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/activity/data/repository/ActivityManagerRepositoryTest.kt
new file mode 100644
index 000000000000..d6ba98d65d15
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/activity/data/repository/ActivityManagerRepositoryTest.kt
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.activity.data.repository
+
+import android.app.ActivityManager
+import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND
+import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE
+import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_TOP_SLEEPING
+import android.app.activityManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ActivityManagerRepositoryTest : SysuiTestCase() {
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+ private val logger = Logger(logcatLogBuffer("ActivityManagerRepositoryTest"), "tag")
+
+ private val Kosmos.underTest by Kosmos.Fixture { realActivityManagerRepository }
+
+ @Test
+ fun createIsAppVisibleFlow_fetchesInitialValue_true() =
+ kosmos.runTest {
+ whenever(activityManager.getUidImportance(THIS_UID)).thenReturn(IMPORTANCE_FOREGROUND)
+
+ val latest by
+ collectLastValue(underTest.createIsAppVisibleFlow(THIS_UID, logger, LOG_TAG))
+
+ assertThat(latest).isTrue()
+ }
+
+ @Test
+ fun createIsAppVisibleFlow_fetchesInitialValue_false() =
+ kosmos.runTest {
+ whenever(activityManager.getUidImportance(THIS_UID)).thenReturn(IMPORTANCE_GONE)
+
+ val latest by
+ collectLastValue(underTest.createIsAppVisibleFlow(THIS_UID, logger, LOG_TAG))
+
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun createIsAppVisibleFlow_getsImportanceUpdates() =
+ kosmos.runTest {
+ val latest by
+ collectLastValue(underTest.createIsAppVisibleFlow(THIS_UID, logger, LOG_TAG))
+
+ val listenerCaptor = argumentCaptor<ActivityManager.OnUidImportanceListener>()
+ verify(activityManager).addOnUidImportanceListener(listenerCaptor.capture(), any())
+ val listener = listenerCaptor.firstValue
+
+ listener.onUidImportance(THIS_UID, IMPORTANCE_GONE)
+ assertThat(latest).isFalse()
+
+ listener.onUidImportance(THIS_UID, IMPORTANCE_FOREGROUND)
+ assertThat(latest).isTrue()
+
+ listener.onUidImportance(THIS_UID, IMPORTANCE_TOP_SLEEPING)
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun createIsAppVisibleFlow_ignoresUpdatesForOtherUids() =
+ kosmos.runTest {
+ val latest by
+ collectLastValue(underTest.createIsAppVisibleFlow(THIS_UID, logger, LOG_TAG))
+
+ val listenerCaptor = argumentCaptor<ActivityManager.OnUidImportanceListener>()
+ verify(activityManager).addOnUidImportanceListener(listenerCaptor.capture(), any())
+ val listener = listenerCaptor.firstValue
+
+ listener.onUidImportance(THIS_UID, IMPORTANCE_GONE)
+ assertThat(latest).isFalse()
+
+ // WHEN another UID becomes foreground
+ listener.onUidImportance(THIS_UID + 2, IMPORTANCE_FOREGROUND)
+
+ // THEN this UID still stays not visible
+ assertThat(latest).isFalse()
+ }
+
+ @Test
+ fun createIsAppVisibleFlow_securityExceptionOnUidRegistration_ok() =
+ kosmos.runTest {
+ whenever(activityManager.getUidImportance(THIS_UID)).thenReturn(IMPORTANCE_GONE)
+ whenever(activityManager.addOnUidImportanceListener(any(), any()))
+ .thenThrow(SecurityException())
+
+ val latest by
+ collectLastValue(underTest.createIsAppVisibleFlow(THIS_UID, logger, LOG_TAG))
+
+ // Verify no crash, and we get a value emitted
+ assertThat(latest).isFalse()
+ }
+
+ /** Regression test for b/216248574. */
+ @Test
+ fun createIsAppVisibleFlow_getUidImportanceThrowsException_ok() =
+ kosmos.runTest {
+ whenever(activityManager.getUidImportance(any())).thenThrow(SecurityException())
+
+ val latest by
+ collectLastValue(underTest.createIsAppVisibleFlow(THIS_UID, logger, LOG_TAG))
+
+ // Verify no crash, and we get a value emitted
+ assertThat(latest).isFalse()
+ }
+
+ companion object {
+ private const val THIS_UID = 558
+ private const val LOG_TAG = "LogTag"
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
index cdda9ccc9b9e..41cc6ee182cf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
@@ -48,8 +48,8 @@ import com.android.systemui.shade.domain.interactor.ShadeBackActionInteractor
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
-import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index 91f9cce5b69b..91f9cce5b69b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt
index 25f956574274..a11dace0505c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.bluetooth.qsdialog
import android.bluetooth.BluetoothLeBroadcast
+import android.bluetooth.BluetoothLeBroadcastMetadata
import android.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -50,6 +51,7 @@ class AudioSharingInteractorTest : SysuiTestCase() {
@get:Rule val mockito: MockitoRule = MockitoJUnit.rule()
private val kosmos = testKosmos()
@Mock private lateinit var localBluetoothLeBroadcast: LocalBluetoothLeBroadcast
+ @Mock private lateinit var bluetoothLeBroadcastMetadata: BluetoothLeBroadcastMetadata
@Captor private lateinit var callbackCaptor: ArgumentCaptor<BluetoothLeBroadcast.Callback>
private lateinit var underTest: AudioSharingInteractor
@@ -202,7 +204,7 @@ class AudioSharingInteractorTest : SysuiTestCase() {
verify(localBluetoothLeBroadcast)
.registerServiceCallBack(any(), callbackCaptor.capture())
runCurrent()
- callbackCaptor.value.onPlaybackStarted(0, 0)
+ callbackCaptor.value.onBroadcastMetadataChanged(0, bluetoothLeBroadcastMetadata)
runCurrent()
assertThat(bluetoothTileDialogAudioSharingRepository.sourceAdded).isTrue()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
index 5bbd3ffc625a..18cc8bf5f0d3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
@@ -172,16 +172,16 @@ class CommunalEditModeViewModelTest : SysuiTestCase() {
}
@Test
- fun selectedKey_onReorderWidgets_isCleared() =
+ fun selectedKey_onReorderWidgets_isSet() =
testScope.runTest {
val selectedKey by collectLastValue(underTest.selectedKey)
+ underTest.setSelectedKey(null)
+ assertThat(selectedKey).isNull()
+
val key = CommunalContentModel.KEY.widget(123)
- underTest.setSelectedKey(key)
+ underTest.onReorderWidgetStart(key)
assertThat(selectedKey).isEqualTo(key)
-
- underTest.onReorderWidgetStart()
- assertThat(selectedKey).isNull()
}
@Test
@@ -234,7 +234,7 @@ class CommunalEditModeViewModelTest : SysuiTestCase() {
@Test
fun reorderWidget_uiEventLogging_start() {
- underTest.onReorderWidgetStart()
+ underTest.onReorderWidgetStart(CommunalContentModel.KEY.widget(123))
verify(uiEventLogger).log(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_START)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/doze/DozeScreenStateTest.java
index ba578a39b558..bbd78b317560 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/doze/DozeScreenStateTest.java
@@ -35,7 +35,7 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Matchers.anyObject;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.verify;
@@ -315,6 +315,6 @@ public class DozeScreenStateTest extends SysuiTestCase {
public void authCallbackRemovedOnDestroy() {
mScreen.destroy();
- verify(mAuthController).removeCallback(anyObject());
+ verify(mAuthController).removeCallback(any());
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModelTest.kt
index 82bcecef1f70..55b87db232e8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModelTest.kt
@@ -27,6 +27,7 @@ import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.communal.domain.interactor.setCommunalAvailable
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
import com.android.systemui.flags.EnableSceneContainer
@@ -72,8 +73,10 @@ class DreamUserActionsViewModelTest : SysuiTestCase() {
@Test
@DisableFlags(DualShade.FLAG_NAME)
- fun actions_singleShade() =
+ fun actions_communalNotAvailable_singleShade() =
testScope.runTest {
+ kosmos.setCommunalAvailable(false)
+
val actions by collectLastValue(underTest.actions)
setUpState(
@@ -85,6 +88,8 @@ class DreamUserActionsViewModelTest : SysuiTestCase() {
assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Bouncer))
assertThat(actions?.get(Swipe.Down))
.isEqualTo(UserActionResult(Scenes.Shade, isIrreversible = true))
+ assertThat(actions?.get(Swipe.Start)).isNull()
+ assertThat(actions?.get(Swipe.End)).isNull()
setUpState(
isShadeTouchable = false,
@@ -102,12 +107,16 @@ class DreamUserActionsViewModelTest : SysuiTestCase() {
assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Gone))
assertThat(actions?.get(Swipe.Down))
.isEqualTo(UserActionResult(Scenes.Shade, isIrreversible = true))
+ assertThat(actions?.get(Swipe.Start)).isNull()
+ assertThat(actions?.get(Swipe.End)).isNull()
}
@Test
@DisableFlags(DualShade.FLAG_NAME)
- fun actions_splitShade() =
+ fun actions_communalNotAvailable_splitShade() =
testScope.runTest {
+ kosmos.setCommunalAvailable(false)
+
val actions by collectLastValue(underTest.actions)
setUpState(
@@ -119,6 +128,8 @@ class DreamUserActionsViewModelTest : SysuiTestCase() {
assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Bouncer))
assertThat(actions?.get(Swipe.Down))
.isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade, isIrreversible = true))
+ assertThat(actions?.get(Swipe.Start)).isNull()
+ assertThat(actions?.get(Swipe.End)).isNull()
setUpState(
isShadeTouchable = false,
@@ -136,12 +147,136 @@ class DreamUserActionsViewModelTest : SysuiTestCase() {
assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Gone))
assertThat(actions?.get(Swipe.Down))
.isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade, isIrreversible = true))
+ assertThat(actions?.get(Swipe.Start)).isNull()
+ assertThat(actions?.get(Swipe.End)).isNull()
}
@Test
@EnableFlags(DualShade.FLAG_NAME)
- fun actions_dualShade() =
+ fun actions_communalNotAvailable_dualShade() =
testScope.runTest {
+ kosmos.setCommunalAvailable(false)
+
+ val actions by collectLastValue(underTest.actions)
+
+ setUpState(
+ isShadeTouchable = true,
+ isDeviceUnlocked = false,
+ shadeMode = ShadeMode.Dual,
+ )
+ assertThat(actions).isNotEmpty()
+ assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Bouncer))
+ assertThat(actions?.get(Swipe.Down))
+ .isEqualTo(
+ UserActionResult.ShowOverlay(Overlays.NotificationsShade, isIrreversible = true)
+ )
+ assertThat(actions?.get(Swipe.Start)).isNull()
+ assertThat(actions?.get(Swipe.End)).isNull()
+
+ setUpState(
+ isShadeTouchable = false,
+ isDeviceUnlocked = false,
+ shadeMode = ShadeMode.Dual,
+ )
+ assertThat(actions).isEmpty()
+
+ setUpState(isShadeTouchable = true, isDeviceUnlocked = true, shadeMode = ShadeMode.Dual)
+ assertThat(actions).isNotEmpty()
+ assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Gone))
+ assertThat(actions?.get(Swipe.Down))
+ .isEqualTo(
+ UserActionResult.ShowOverlay(Overlays.NotificationsShade, isIrreversible = true)
+ )
+ assertThat(actions?.get(Swipe.Start)).isNull()
+ assertThat(actions?.get(Swipe.End)).isNull()
+ }
+
+ @Test
+ @DisableFlags(DualShade.FLAG_NAME)
+ fun actions_communalAvailable_singleShade() =
+ testScope.runTest {
+ kosmos.setCommunalAvailable(true)
+
+ val actions by collectLastValue(underTest.actions)
+
+ setUpState(
+ isShadeTouchable = true,
+ isDeviceUnlocked = false,
+ shadeMode = ShadeMode.Single,
+ )
+ assertThat(actions).isNotEmpty()
+ assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Bouncer))
+ assertThat(actions?.get(Swipe.Down))
+ .isEqualTo(UserActionResult(Scenes.Shade, isIrreversible = true))
+ assertThat(actions?.get(Swipe.Start)).isEqualTo(UserActionResult(Scenes.Communal))
+ assertThat(actions?.get(Swipe.End)).isNull()
+
+ setUpState(
+ isShadeTouchable = false,
+ isDeviceUnlocked = false,
+ shadeMode = ShadeMode.Single,
+ )
+ assertThat(actions).isEmpty()
+
+ setUpState(
+ isShadeTouchable = true,
+ isDeviceUnlocked = true,
+ shadeMode = ShadeMode.Single,
+ )
+ assertThat(actions).isNotEmpty()
+ assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Gone))
+ assertThat(actions?.get(Swipe.Down))
+ .isEqualTo(UserActionResult(Scenes.Shade, isIrreversible = true))
+ assertThat(actions?.get(Swipe.Start)).isEqualTo(UserActionResult(Scenes.Communal))
+ assertThat(actions?.get(Swipe.End)).isNull()
+ }
+
+ @Test
+ @DisableFlags(DualShade.FLAG_NAME)
+ fun actions_communalAvailable_splitShade() =
+ testScope.runTest {
+ kosmos.setCommunalAvailable(true)
+
+ val actions by collectLastValue(underTest.actions)
+
+ setUpState(
+ isShadeTouchable = true,
+ isDeviceUnlocked = false,
+ shadeMode = ShadeMode.Split,
+ )
+ assertThat(actions).isNotEmpty()
+ assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Bouncer))
+ assertThat(actions?.get(Swipe.Down))
+ .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade, isIrreversible = true))
+ assertThat(actions?.get(Swipe.Start)).isEqualTo(UserActionResult(Scenes.Communal))
+ assertThat(actions?.get(Swipe.End)).isNull()
+
+ setUpState(
+ isShadeTouchable = false,
+ isDeviceUnlocked = false,
+ shadeMode = ShadeMode.Split,
+ )
+ assertThat(actions).isEmpty()
+
+ setUpState(
+ isShadeTouchable = true,
+ isDeviceUnlocked = true,
+ shadeMode = ShadeMode.Split,
+ )
+ assertThat(actions).isNotEmpty()
+ assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Gone))
+ assertThat(actions?.get(Swipe.Down))
+ .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade, isIrreversible = true))
+ assertThat(actions?.get(Swipe.Start)).isEqualTo(UserActionResult(Scenes.Communal))
+ assertThat(actions?.get(Swipe.End)).isNull()
+ }
+
+ @Test
+ @EnableFlags(DualShade.FLAG_NAME)
+ fun actions_communalAvailable_dualShade() =
+ testScope.runTest {
+ kosmos.setCommunalAvailable(true)
+
val actions by collectLastValue(underTest.actions)
setUpState(
@@ -155,6 +290,8 @@ class DreamUserActionsViewModelTest : SysuiTestCase() {
.isEqualTo(
UserActionResult.ShowOverlay(Overlays.NotificationsShade, isIrreversible = true)
)
+ assertThat(actions?.get(Swipe.Start)).isEqualTo(UserActionResult(Scenes.Communal))
+ assertThat(actions?.get(Swipe.End)).isNull()
setUpState(
isShadeTouchable = false,
@@ -170,6 +307,8 @@ class DreamUserActionsViewModelTest : SysuiTestCase() {
.isEqualTo(
UserActionResult.ShowOverlay(Overlays.NotificationsShade, isIrreversible = true)
)
+ assertThat(actions?.get(Swipe.Start)).isEqualTo(UserActionResult(Scenes.Communal))
+ assertThat(actions?.get(Swipe.End)).isNull()
}
private fun TestScope.setUpState(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt
index dd8370231ef0..e288522ec212 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt
@@ -25,6 +25,7 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.contextualeducation.GestureType
import com.android.systemui.contextualeducation.GestureType.BACK
+import com.android.systemui.contextualeducation.GestureType.HOME
import com.android.systemui.education.data.repository.fakeEduClock
import com.android.systemui.education.domain.interactor.KeyboardTouchpadEduInteractor
import com.android.systemui.education.domain.interactor.contextualEducationInteractor
@@ -52,6 +53,7 @@ import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.junit.MockitoJUnit
import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
@@ -66,6 +68,7 @@ class ContextualEduUiCoordinatorTest : SysuiTestCase() {
private val minDurationForNextEdu =
KeyboardTouchpadEduInteractor.minIntervalBetweenEdu + 1.seconds
private lateinit var underTest: ContextualEduUiCoordinator
+ private lateinit var previousDialog: Dialog
@Mock private lateinit var dialog: Dialog
@Mock private lateinit var notificationManager: NotificationManager
@Mock private lateinit var accessibilityManagerWrapper: AccessibilityManagerWrapper
@@ -95,9 +98,11 @@ class ContextualEduUiCoordinatorTest : SysuiTestCase() {
kosmos.applicationCoroutineScope,
viewModel,
kosmos.applicationContext,
- notificationManager
+ notificationManager,
) { model ->
toastContent = model.message
+ previousDialog = dialog
+ dialog = mock<Dialog>()
dialog
}
underTest.start()
@@ -129,6 +134,14 @@ class ContextualEduUiCoordinatorTest : SysuiTestCase() {
}
@Test
+ fun dismissPreviousDialogOnNewDialog() =
+ testScope.runTest {
+ triggerEducation(BACK)
+ triggerEducation(HOME)
+ verify(previousDialog).dismiss()
+ }
+
+ @Test
fun verifyBackEduToastContent() =
testScope.runTest {
triggerEducation(BACK)
@@ -149,14 +162,14 @@ class ContextualEduUiCoordinatorTest : SysuiTestCase() {
verifyNotificationContent(
R.string.back_edu_notification_title,
R.string.back_edu_notification_content,
- notificationCaptor.value
+ notificationCaptor.value,
)
}
private fun verifyNotificationContent(
titleResId: Int,
contentResId: Int,
- notification: Notification
+ notification: Notification,
) {
val expectedContent = context.getString(contentResId)
val expectedTitle = context.getString(titleResId)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt
index f04540426fc1..0d32b7fb1b3e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepositoryTest.kt
@@ -18,36 +18,31 @@ package com.android.systemui.keyboard.shortcut.data.repository
import android.content.Context
import android.content.Context.INPUT_SERVICE
-import android.hardware.input.InputGestureData
-import android.hardware.input.InputGestureData.createKeyTrigger
-import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS
+import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_SUCCESS
import android.hardware.input.fakeInputManager
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
-import android.view.KeyEvent.KEYCODE_A
import android.view.KeyEvent.KEYCODE_SLASH
-import android.view.KeyEvent.META_ALT_ON
import android.view.KeyEvent.META_CAPS_LOCK_ON
-import android.view.KeyEvent.META_CTRL_ON
-import android.view.KeyEvent.META_FUNCTION_ON
-import android.view.KeyEvent.META_META_LEFT_ON
import android.view.KeyEvent.META_META_ON
-import android.view.KeyEvent.META_SHIFT_ON
-import android.view.KeyEvent.META_SHIFT_RIGHT_ON
-import android.view.KeyEvent.META_SYM_ON
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.hardware.input.Flags.FLAG_ENABLE_CUSTOMIZABLE_INPUT_GESTURES
import com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyboard.shared.model.ShortcutCustomizationRequestResult
import com.android.systemui.keyboard.shortcut.customShortcutCategoriesRepository
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.ALL_SUPPORTED_MODIFIERS
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allAppsInputGestureData
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allCustomizableInputGesturesWithSimpleShortcutCombinations
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.customizableInputGestureWithUnknownKeyGestureType
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.expectedShortcutCategoriesWithSimpleShortcutCombination
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.goHomeInputGestureData
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.standardAddCustomShortcutRequestInfo
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.standardDeleteCustomShortcutRequestInfo
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.standardKeyCombination
import com.android.systemui.keyboard.shortcut.shared.model.KeyCombination
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
import com.android.systemui.keyboard.shortcut.shortcutHelperTestHelper
import com.android.systemui.kosmos.testScope
@@ -74,24 +69,21 @@ class CustomShortcutCategoriesRepositoryTest : SysuiTestCase() {
it.userTracker = FakeUserTracker(onCreateCurrentUserContext = { mockUserContext })
}
- private val fakeInputManager = kosmos.fakeInputManager
+ private val inputManager = kosmos.fakeInputManager.inputManager
private val testScope = kosmos.testScope
private val helper = kosmos.shortcutHelperTestHelper
private val repo = kosmos.customShortcutCategoriesRepository
@Before
fun setup() {
- whenever(mockUserContext.getSystemService(INPUT_SERVICE))
- .thenReturn(fakeInputManager.inputManager)
+ whenever(mockUserContext.getSystemService(INPUT_SERVICE)).thenReturn(inputManager)
}
@Test
@EnableFlags(FLAG_ENABLE_CUSTOMIZABLE_INPUT_GESTURES, FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
fun categories_emitsCorrectlyConvertedShortcutCategories() {
testScope.runTest {
- whenever(
- fakeInputManager.inputManager.getCustomInputGestures(/* filter= */ anyOrNull())
- )
+ whenever(inputManager.getCustomInputGestures(/* filter= */ anyOrNull()))
.thenReturn(allCustomizableInputGesturesWithSimpleShortcutCombinations)
helper.toggle(deviceId = 123)
@@ -106,9 +98,7 @@ class CustomShortcutCategoriesRepositoryTest : SysuiTestCase() {
@DisableFlags(FLAG_ENABLE_CUSTOMIZABLE_INPUT_GESTURES, FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
fun categories_emitsEmptyListWhenFlagIsDisabled() {
testScope.runTest {
- whenever(
- fakeInputManager.inputManager.getCustomInputGestures(/* filter= */ anyOrNull())
- )
+ whenever(inputManager.getCustomInputGestures(/* filter= */ anyOrNull()))
.thenReturn(allCustomizableInputGesturesWithSimpleShortcutCombinations)
helper.toggle(deviceId = 123)
@@ -122,9 +112,7 @@ class CustomShortcutCategoriesRepositoryTest : SysuiTestCase() {
@EnableFlags(FLAG_ENABLE_CUSTOMIZABLE_INPUT_GESTURES, FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
fun categories_ignoresUnknownKeyGestureTypes() {
testScope.runTest {
- whenever(
- fakeInputManager.inputManager.getCustomInputGestures(/* filter= */ anyOrNull())
- )
+ whenever(inputManager.getCustomInputGestures(/* filter= */ anyOrNull()))
.thenReturn(customizableInputGestureWithUnknownKeyGestureType)
helper.toggle(deviceId = 123)
@@ -151,7 +139,7 @@ class CustomShortcutCategoriesRepositoryTest : SysuiTestCase() {
helper.toggle(deviceId = 123)
val pressedKeys by collectLastValue(repo.pressedKeys)
repo.updateUserKeyCombination(
- KeyCombination(modifiers = allSupportedModifiers, keyCode = null)
+ KeyCombination(modifiers = ALL_SUPPORTED_MODIFIERS, keyCode = null)
)
assertThat(pressedKeys)
@@ -199,11 +187,11 @@ class CustomShortcutCategoriesRepositoryTest : SysuiTestCase() {
@Test
fun shortcutBeingCustomized_updatedOnCustomizationRequested() {
testScope.runTest {
- repo.onCustomizationRequested(standardCustomizationRequestInfo)
+ repo.onCustomizationRequested(standardAddCustomShortcutRequestInfo)
val shortcutBeingCustomized = repo.getShortcutBeingCustomized()
- assertThat(shortcutBeingCustomized).isEqualTo(standardCustomizationRequestInfo)
+ assertThat(shortcutBeingCustomized).isEqualTo(standardAddCustomShortcutRequestInfo)
}
}
@@ -223,7 +211,7 @@ class CustomShortcutCategoriesRepositoryTest : SysuiTestCase() {
fun buildInputGestureDataForShortcutBeingCustomized_noKeyCombinationSelected_returnsNull() {
testScope.runTest {
helper.toggle(deviceId = 123)
- repo.onCustomizationRequested(standardCustomizationRequestInfo)
+ repo.onCustomizationRequested(standardAddCustomShortcutRequestInfo)
val inputGestureData = repo.buildInputGestureDataForShortcutBeingCustomized()
@@ -235,46 +223,32 @@ class CustomShortcutCategoriesRepositoryTest : SysuiTestCase() {
fun buildInputGestureDataForShortcutBeingCustomized_successfullyBuildInputGestureData() {
testScope.runTest {
helper.toggle(deviceId = 123)
- repo.onCustomizationRequested(standardCustomizationRequestInfo)
+ repo.onCustomizationRequested(standardAddCustomShortcutRequestInfo)
repo.updateUserKeyCombination(standardKeyCombination)
val inputGestureData = repo.buildInputGestureDataForShortcutBeingCustomized()
// using toString as we're testing for only structural equality not referential.
// inputGestureData is a java class and isEqual Tests for referential equality
// as well which would cause this assert to fail
- assertThat(inputGestureData.toString()).isEqualTo(standardInputGestureData.toString())
+ assertThat(inputGestureData.toString()).isEqualTo(allAppsInputGestureData.toString())
}
}
- private val standardCustomizationRequestInfo =
- ShortcutCustomizationRequestInfo.Add(
- label = "Open apps list",
- categoryType = ShortcutCategoryType.System,
- subCategoryLabel = "System controls",
- )
-
- private val standardKeyCombination =
- KeyCombination(
- modifiers = META_META_ON or META_SHIFT_ON or META_META_LEFT_ON or META_SHIFT_RIGHT_ON,
- keyCode = KEYCODE_A,
- )
-
- private val allSupportedModifiers =
- META_META_ON or
- META_CTRL_ON or
- META_FUNCTION_ON or
- META_SHIFT_ON or
- META_ALT_ON or
- META_SYM_ON
-
- private val standardInputGestureData =
- InputGestureData.Builder()
- .setKeyGestureType(KEY_GESTURE_TYPE_ALL_APPS)
- .setTrigger(
- createKeyTrigger(
- /* keycode = */ standardKeyCombination.keyCode!!,
- /* modifierState = */ standardKeyCombination.modifiers and allSupportedModifiers,
- )
- )
- .build()
+ @Test
+ @EnableFlags(FLAG_ENABLE_CUSTOMIZABLE_INPUT_GESTURES, FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
+ fun deleteShortcut_successfullyRetrievesGestureDataAndDeletesShortcut() {
+ testScope.runTest {
+ whenever(inputManager.getCustomInputGestures(anyOrNull()))
+ .thenReturn(listOf(allAppsInputGestureData, goHomeInputGestureData))
+ whenever(inputManager.removeCustomInputGesture(allAppsInputGestureData))
+ .thenReturn(CUSTOM_INPUT_GESTURE_RESULT_SUCCESS)
+
+ helper.toggle(deviceId = 123)
+ repo.onCustomizationRequested(standardDeleteCustomShortcutRequestInfo)
+
+ val result = repo.deleteShortcutCurrentlyBeingCustomized()
+
+ assertThat(result).isEqualTo(ShortcutCustomizationRequestResult.SUCCESS)
+ }
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt
index a1e7ef4ac5a3..8466eab2aca6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt
@@ -19,9 +19,23 @@ package com.android.systemui.keyboard.shortcut.data.source
import android.hardware.input.InputGestureData
import android.hardware.input.InputGestureData.createKeyTrigger
import android.hardware.input.KeyGestureEvent
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_HOME
+import android.os.SystemClock
import android.view.KeyEvent
+import android.view.KeyEvent.ACTION_DOWN
+import android.view.KeyEvent.KEYCODE_A
+import android.view.KeyEvent.META_ALT_ON
+import android.view.KeyEvent.META_CTRL_ON
+import android.view.KeyEvent.META_FUNCTION_ON
+import android.view.KeyEvent.META_META_LEFT_ON
+import android.view.KeyEvent.META_META_ON
+import android.view.KeyEvent.META_SHIFT_ON
+import android.view.KeyEvent.META_SHIFT_RIGHT_ON
+import android.view.KeyEvent.META_SYM_ON
import android.view.KeyboardShortcutGroup
import android.view.KeyboardShortcutInfo
+import com.android.systemui.keyboard.shortcut.shared.model.KeyCombination
import com.android.systemui.keyboard.shortcut.shared.model.Shortcut
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
@@ -29,9 +43,11 @@ import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.MultiTasking
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.System
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCommand
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
import com.android.systemui.keyboard.shortcut.shared.model.shortcut
+import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState
import com.android.systemui.res.R
object TestShortcuts {
@@ -525,4 +541,110 @@ object TestShortcuts {
keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER
),
)
+
+ val standardAddCustomShortcutRequestInfo =
+ ShortcutCustomizationRequestInfo.Add(
+ label = "Open apps list",
+ categoryType = System,
+ subCategoryLabel = "System controls",
+ )
+
+ val standardDeleteCustomShortcutRequestInfo =
+ ShortcutCustomizationRequestInfo.Delete(
+ label = "Open apps list",
+ categoryType = System,
+ subCategoryLabel = "System controls",
+ )
+
+ val standardKeyCombination =
+ KeyCombination(
+ modifiers = META_META_ON or META_SHIFT_ON or META_META_LEFT_ON or META_SHIFT_RIGHT_ON,
+ keyCode = KEYCODE_A,
+ )
+
+ const val ALL_SUPPORTED_MODIFIERS =
+ META_META_ON or
+ META_CTRL_ON or
+ META_FUNCTION_ON or
+ META_SHIFT_ON or
+ META_ALT_ON or
+ META_SYM_ON
+
+ val allAppsInputGestureData: InputGestureData =
+ InputGestureData.Builder()
+ .setKeyGestureType(KEY_GESTURE_TYPE_ALL_APPS)
+ .setTrigger(
+ createKeyTrigger(
+ /* keycode = */ standardKeyCombination.keyCode!!,
+ /* modifierState = */ standardKeyCombination.modifiers and
+ ALL_SUPPORTED_MODIFIERS,
+ )
+ )
+ .build()
+
+ val goHomeInputGestureData: InputGestureData =
+ InputGestureData.Builder()
+ .setKeyGestureType(KEY_GESTURE_TYPE_HOME)
+ .setTrigger(
+ createKeyTrigger(
+ /* keycode = */ standardKeyCombination.keyCode!!,
+ /* modifierState = */ standardKeyCombination.modifiers and
+ ALL_SUPPORTED_MODIFIERS,
+ )
+ )
+ .build()
+
+ val expectedStandardDeleteShortcutUiState =
+ ShortcutCustomizationUiState.DeleteShortcutDialog(isDialogShowing = false)
+
+ val keyDownEventWithoutActionKeyPressed =
+ androidx.compose.ui.input.key.KeyEvent(
+ android.view.KeyEvent(
+ /* downTime = */ SystemClock.uptimeMillis(),
+ /* eventTime = */ SystemClock.uptimeMillis(),
+ /* action = */ ACTION_DOWN,
+ /* code = */ KEYCODE_A,
+ /* repeat = */ 0,
+ /* metaState = */ META_CTRL_ON,
+ )
+ )
+
+ val keyDownEventWithActionKeyPressed =
+ androidx.compose.ui.input.key.KeyEvent(
+ android.view.KeyEvent(
+ /* downTime = */ SystemClock.uptimeMillis(),
+ /* eventTime = */ SystemClock.uptimeMillis(),
+ /* action = */ ACTION_DOWN,
+ /* code = */ KEYCODE_A,
+ /* repeat = */ 0,
+ /* metaState = */ META_CTRL_ON or META_META_ON,
+ )
+ )
+
+ val keyUpEventWithActionKeyPressed =
+ androidx.compose.ui.input.key.KeyEvent(
+ android.view.KeyEvent(
+ /* downTime = */ SystemClock.uptimeMillis(),
+ /* eventTime = */ SystemClock.uptimeMillis(),
+ /* action = */ ACTION_DOWN,
+ /* code = */ KEYCODE_A,
+ /* repeat = */ 0,
+ /* metaState = */ 0,
+ )
+ )
+
+ val standardAddShortcutRequest =
+ ShortcutCustomizationRequestInfo.Add(
+ label = "Standard shortcut",
+ categoryType = ShortcutCategoryType.System,
+ subCategoryLabel = "Standard subcategory",
+ )
+
+ val expectedStandardAddShortcutUiState =
+ ShortcutCustomizationUiState.AddShortcutDialog(
+ shortcutLabel = "Standard shortcut",
+ defaultCustomShortcutModifierKey =
+ ShortcutKey.Icon.ResIdIcon(R.drawable.ic_ksh_key_meta),
+ isDialogShowing = false,
+ )
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt
index f706cf6980e1..d0ce34c2d68d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModelTest.kt
@@ -23,18 +23,20 @@ import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_OTH
import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_RESERVED_GESTURE
import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_SUCCESS
import android.hardware.input.fakeInputManager
-import android.os.SystemClock
-import android.view.KeyEvent.ACTION_DOWN
-import android.view.KeyEvent.KEYCODE_A
-import android.view.KeyEvent.META_CTRL_ON
-import android.view.KeyEvent.META_META_ON
-import androidx.compose.ui.input.key.KeyEvent
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.keyboard.shortcut.shared.model.ShortcutCategoryType
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allAppsInputGestureData
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.expectedStandardAddShortcutUiState
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.expectedStandardDeleteShortcutUiState
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.goHomeInputGestureData
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.keyDownEventWithActionKeyPressed
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.keyDownEventWithoutActionKeyPressed
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.keyUpEventWithActionKeyPressed
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.standardAddCustomShortcutRequestInfo
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.standardAddShortcutRequest
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.standardDeleteCustomShortcutRequestInfo
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
import com.android.systemui.keyboard.shortcut.shortcutCustomizationViewModelFactory
import com.android.systemui.keyboard.shortcut.shortcutHelperTestHelper
@@ -97,7 +99,7 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() {
@Test
fun uiState_correctlyUpdatedWhenDeleteShortcutCustomizationIsRequested() {
testScope.runTest {
- viewModel.onShortcutCustomizationRequested(standardDeleteShortcutRequest)
+ viewModel.onShortcutCustomizationRequested(standardDeleteCustomShortcutRequestInfo)
val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
assertThat(uiState).isEqualTo(expectedStandardDeleteShortcutUiState)
@@ -120,7 +122,7 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() {
fun uiState_consumedOnDeleteDialogShown() {
testScope.runTest {
val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
- viewModel.onShortcutCustomizationRequested(standardDeleteShortcutRequest)
+ viewModel.onShortcutCustomizationRequested(standardDeleteCustomShortcutRequestInfo)
viewModel.onDialogShown()
assertThat(
@@ -168,7 +170,7 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() {
fun uiState_errorMessage_isEmptyByDefault() {
testScope.runTest {
val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
- viewModel.onShortcutCustomizationRequested(allAppsShortcutCustomizationRequest)
+ viewModel.onShortcutCustomizationRequested(standardAddCustomShortcutRequestInfo)
viewModel.onDialogShown()
assertThat((uiState as ShortcutCustomizationUiState.AddShortcutDialog).errorMessage)
@@ -227,6 +229,21 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() {
}
@Test
+ fun uiState_becomesInactiveAfterSuccessfullyDeletingShortcut() {
+ testScope.runTest {
+ val uiState by collectLastValue(viewModel.shortcutCustomizationUiState)
+ whenever(inputManager.getCustomInputGestures(any()))
+ .thenReturn(listOf(goHomeInputGestureData, allAppsInputGestureData))
+ whenever(inputManager.removeCustomInputGesture(any()))
+ .thenReturn(CUSTOM_INPUT_GESTURE_RESULT_SUCCESS)
+
+ openDeleteShortcutDialogAndDeleteShortcut()
+
+ assertThat(uiState).isEqualTo(ShortcutCustomizationUiState.Inactive)
+ }
+ }
+
+ @Test
fun onKeyPressed_handlesKeyEvents_whereActionKeyIsAlsoPressed() {
testScope.runTest {
viewModel.onShortcutCustomizationRequested(standardAddShortcutRequest)
@@ -281,7 +298,7 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() {
}
private suspend fun openAddShortcutDialogAndSetShortcut() {
- viewModel.onShortcutCustomizationRequested(allAppsShortcutCustomizationRequest)
+ viewModel.onShortcutCustomizationRequested(standardAddCustomShortcutRequestInfo)
viewModel.onDialogShown()
viewModel.onKeyPressed(keyDownEventWithActionKeyPressed)
@@ -290,71 +307,10 @@ class ShortcutCustomizationViewModelTest : SysuiTestCase() {
viewModel.onSetShortcut()
}
- private val keyDownEventWithoutActionKeyPressed =
- KeyEvent(
- android.view.KeyEvent(
- /* downTime = */ SystemClock.uptimeMillis(),
- /* eventTime = */ SystemClock.uptimeMillis(),
- /* action = */ ACTION_DOWN,
- /* code = */ KEYCODE_A,
- /* repeat = */ 0,
- /* metaState = */ META_CTRL_ON,
- )
- )
-
- private val keyDownEventWithActionKeyPressed =
- KeyEvent(
- android.view.KeyEvent(
- /* downTime = */ SystemClock.uptimeMillis(),
- /* eventTime = */ SystemClock.uptimeMillis(),
- /* action = */ ACTION_DOWN,
- /* code = */ KEYCODE_A,
- /* repeat = */ 0,
- /* metaState = */ META_CTRL_ON or META_META_ON,
- )
- )
-
- private val keyUpEventWithActionKeyPressed =
- KeyEvent(
- android.view.KeyEvent(
- /* downTime = */ SystemClock.uptimeMillis(),
- /* eventTime = */ SystemClock.uptimeMillis(),
- /* action = */ ACTION_DOWN,
- /* code = */ KEYCODE_A,
- /* repeat = */ 0,
- /* metaState = */ 0,
- )
- )
-
- private val standardAddShortcutRequest =
- ShortcutCustomizationRequestInfo.Add(
- label = "Standard shortcut",
- categoryType = ShortcutCategoryType.System,
- subCategoryLabel = "Standard subcategory",
- )
-
- private val standardDeleteShortcutRequest =
- ShortcutCustomizationRequestInfo.Delete(
- label = "Standard shortcut",
- categoryType = ShortcutCategoryType.System,
- subCategoryLabel = "Standard subcategory",
- )
-
- private val allAppsShortcutCustomizationRequest =
- ShortcutCustomizationRequestInfo.Add(
- label = "Open apps list",
- categoryType = ShortcutCategoryType.System,
- subCategoryLabel = "System controls",
- )
-
- private val expectedStandardAddShortcutUiState =
- ShortcutCustomizationUiState.AddShortcutDialog(
- shortcutLabel = "Standard shortcut",
- defaultCustomShortcutModifierKey =
- ShortcutKey.Icon.ResIdIcon(R.drawable.ic_ksh_key_meta),
- isDialogShowing = false,
- )
-
- private val expectedStandardDeleteShortcutUiState =
- ShortcutCustomizationUiState.DeleteShortcutDialog(isDialogShowing = false)
+ private suspend fun openDeleteShortcutDialogAndDeleteShortcut() {
+ viewModel.onShortcutCustomizationRequested(standardDeleteCustomShortcutRequestInfo)
+ viewModel.onDialogShown()
+
+ viewModel.deleteShortcutCurrentlyBeingCustomized()
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLockWhileAwakeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLockWhileAwakeInteractorTest.kt
index bd26e4205242..bef995fa5225 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLockWhileAwakeInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLockWhileAwakeInteractorTest.kt
@@ -18,6 +18,7 @@ package com.android.systemui.keyguard.domain.interactor
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.internal.widget.lockPatternUtils
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectValues
import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
@@ -33,6 +34,8 @@ import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.kotlin.whenever
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -52,14 +55,21 @@ class KeyguardLockWhileAwakeInteractorTest : SysuiTestCase() {
testScope.runTest {
val values by collectValues(underTest.lockWhileAwakeEvents)
- underTest.onKeyguardServiceDoKeyguardTimeout(options = null)
+ kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(true)
+ runCurrent()
+
+ kosmos.keyguardServiceLockNowInteractor.onKeyguardServiceDoKeyguardTimeout(
+ options = null
+ )
runCurrent()
assertThat(values)
.containsExactly(LockWhileAwakeReason.KEYGUARD_TIMEOUT_WHILE_SCREEN_ON)
advanceTimeBy(1000)
- underTest.onKeyguardServiceDoKeyguardTimeout(options = null)
+ kosmos.keyguardServiceLockNowInteractor.onKeyguardServiceDoKeyguardTimeout(
+ options = null
+ )
runCurrent()
assertThat(values)
@@ -69,8 +79,15 @@ class KeyguardLockWhileAwakeInteractorTest : SysuiTestCase() {
)
}
+ /**
+ * We re-show keyguard when it's re-enabled, but only if it was originally showing when we
+ * disabled it.
+ *
+ * If it wasn't showing when originally disabled it, re-enabling it should do nothing (the
+ * keyguard will re-show next time we're locked).
+ */
@Test
- fun emitsWhenKeyguardEnabled_onlyIfShowingWhenDisabled() =
+ fun emitsWhenKeyguardReenabled_onlyIfShowingWhenDisabled() =
testScope.runTest {
val values by collectValues(underTest.lockWhileAwakeEvents)
@@ -98,4 +115,49 @@ class KeyguardLockWhileAwakeInteractorTest : SysuiTestCase() {
assertThat(values).containsExactly(LockWhileAwakeReason.KEYGUARD_REENABLED)
}
+
+ /**
+ * Un-suppressing keyguard should never cause us to re-show. We'll re-show when we're next
+ * locked, even if we were showing when originally suppressed.
+ */
+ @Test
+ fun doesNotEmit_keyguardNoLongerSuppressed() =
+ testScope.runTest {
+ val values by collectValues(underTest.lockWhileAwakeEvents)
+
+ // Enable keyguard and then suppress it.
+ kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(true)
+ whenever(kosmos.lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(true)
+ runCurrent()
+
+ assertEquals(0, values.size)
+
+ // Un-suppress keyguard.
+ whenever(kosmos.lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(false)
+ runCurrent()
+
+ assertEquals(0, values.size)
+ }
+
+ /**
+ * Lockdown and lockNow() should not cause us to lock while awake if we are suppressed via adb.
+ */
+ @Test
+ fun doesNotEmit_fromLockdown_orFromLockNow_ifEnabledButSuppressed() =
+ testScope.runTest {
+ val values by collectValues(underTest.lockWhileAwakeEvents)
+
+ // Set keyguard enabled, but then disable lockscreen (suppress it).
+ kosmos.keyguardEnabledInteractor.notifyKeyguardEnabled(true)
+ whenever(kosmos.lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(true)
+ runCurrent()
+
+ kosmos.keyguardServiceLockNowInteractor.onKeyguardServiceDoKeyguardTimeout(null)
+ runCurrent()
+
+ kosmos.biometricSettingsRepository.setIsUserInLockdown(true)
+ runCurrent()
+
+ assertEquals(0, values.size)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorTest.kt
index 7e249e8c179d..ead151ee6df2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorTest.kt
@@ -87,9 +87,9 @@ class KeyguardWakeDirectlyToGoneInteractorTest : SysuiTestCase() {
assertEquals(
listOf(
- false, // Defaults to false.
+ false // Defaults to false.
),
- canWake
+ canWake,
)
repository.setKeyguardEnabled(false)
@@ -100,33 +100,26 @@ class KeyguardWakeDirectlyToGoneInteractorTest : SysuiTestCase() {
false, // Default to false.
true, // True now that keyguard service is disabled
),
- canWake
+ canWake,
)
repository.setKeyguardEnabled(true)
runCurrent()
- assertEquals(
- listOf(
- false,
- true,
- false,
- ),
- canWake
- )
+ assertEquals(listOf(false, true, false), canWake)
}
@Test
@EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
- fun testCanWakeDirectlyToGone_lockscreenDisabledThenEnabled() =
+ fun testCanWakeDirectlyToGone_lockscreenDisabledThenEnabled_onlyAfterWakefulnessChange() =
testScope.runTest {
val canWake by collectValues(underTest.canWakeDirectlyToGone)
assertEquals(
listOf(
- false, // Defaults to false.
+ false // Defaults to false.
),
- canWake
+ canWake,
)
whenever(lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(true)
@@ -136,9 +129,9 @@ class KeyguardWakeDirectlyToGoneInteractorTest : SysuiTestCase() {
listOf(
// Still false - isLockScreenDisabled only causes canWakeDirectlyToGone to
// update on the next wake/sleep event.
- false,
+ false
),
- canWake
+ canWake,
)
kosmos.powerInteractor.setAsleepForTest()
@@ -150,7 +143,7 @@ class KeyguardWakeDirectlyToGoneInteractorTest : SysuiTestCase() {
// True since we slept after setting isLockScreenDisabled=true
true,
),
- canWake
+ canWake,
)
kosmos.powerInteractor.setAwakeForTest()
@@ -159,25 +152,75 @@ class KeyguardWakeDirectlyToGoneInteractorTest : SysuiTestCase() {
kosmos.powerInteractor.setAsleepForTest()
runCurrent()
+ assertEquals(listOf(false, true), canWake)
+
+ whenever(lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(false)
+ kosmos.powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ assertEquals(listOf(false, true, false), canWake)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testCanWakeDirectlyToGone_lockscreenDisabledThenEnabled_lockNowEvent() =
+ testScope.runTest {
+ val canWake by collectValues(underTest.canWakeDirectlyToGone)
+
+ assertEquals(
+ listOf(
+ false // Defaults to false.
+ ),
+ canWake,
+ )
+
+ whenever(lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(true)
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ // Still false - isLockScreenDisabled only causes canWakeDirectlyToGone to
+ // update on the next wakefulness or lockNow event.
+ false
+ ),
+ canWake,
+ )
+
+ kosmos.keyguardServiceLockNowInteractor.onKeyguardServiceDoKeyguardTimeout(null)
+ runCurrent()
+
assertEquals(
listOf(
false,
+ // True when lockNow() called after setting isLockScreenDisabled=true
true,
),
- canWake
+ canWake,
)
whenever(lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(false)
- kosmos.powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ false,
+ // Still true since no lockNow() calls made.
+ true,
+ ),
+ canWake,
+ )
+
+ kosmos.keyguardServiceLockNowInteractor.onKeyguardServiceDoKeyguardTimeout(null)
runCurrent()
assertEquals(
listOf(
false,
true,
+ // False again after the lockNow() call.
false,
),
- canWake
+ canWake,
)
}
@@ -189,9 +232,9 @@ class KeyguardWakeDirectlyToGoneInteractorTest : SysuiTestCase() {
assertEquals(
listOf(
- false, // Defaults to false.
+ false // Defaults to false.
),
- canWake
+ canWake,
)
repository.setBiometricUnlockState(BiometricUnlockMode.WAKE_AND_UNLOCK)
@@ -213,9 +256,9 @@ class KeyguardWakeDirectlyToGoneInteractorTest : SysuiTestCase() {
assertEquals(
listOf(
- false, // Defaults to false.
+ false // Defaults to false.
),
- canWake
+ canWake,
)
repository.setCanIgnoreAuthAndReturnToGone(true)
@@ -237,9 +280,9 @@ class KeyguardWakeDirectlyToGoneInteractorTest : SysuiTestCase() {
assertEquals(
listOf(
- false, // Defaults to false.
+ false // Defaults to false.
),
- canWake
+ canWake,
)
whenever(kosmos.devicePolicyManager.getMaximumTimeToLock(eq(null), anyInt()))
@@ -257,13 +300,7 @@ class KeyguardWakeDirectlyToGoneInteractorTest : SysuiTestCase() {
)
runCurrent()
- assertEquals(
- listOf(
- false,
- true,
- ),
- canWake
- )
+ assertEquals(listOf(false, true), canWake)
verify(kosmos.alarmManager)
.setExactAndAllowWhileIdle(
@@ -281,9 +318,9 @@ class KeyguardWakeDirectlyToGoneInteractorTest : SysuiTestCase() {
assertEquals(
listOf(
- false, // Defaults to false.
+ false // Defaults to false.
),
- canWake
+ canWake,
)
whenever(kosmos.devicePolicyManager.getMaximumTimeToLock(eq(null), anyInt()))
@@ -312,7 +349,7 @@ class KeyguardWakeDirectlyToGoneInteractorTest : SysuiTestCase() {
// Timed out, so we can ignore auth/return to GONE.
true,
),
- canWake
+ canWake,
)
verify(kosmos.alarmManager)
@@ -338,7 +375,7 @@ class KeyguardWakeDirectlyToGoneInteractorTest : SysuiTestCase() {
// alarm in flight that should be canceled.
false,
),
- canWake
+ canWake,
)
kosmos.powerInteractor.setAsleepForTest(
@@ -354,25 +391,17 @@ class KeyguardWakeDirectlyToGoneInteractorTest : SysuiTestCase() {
// Back to sleep.
true,
),
- canWake
+ canWake,
)
// Simulate the first sleep's alarm coming in.
lastRegisteredBroadcastReceiver?.onReceive(
kosmos.mockedContext,
- Intent("com.android.internal.policy.impl.PhoneWindowManager.DELAYED_KEYGUARD")
+ Intent("com.android.internal.policy.impl.PhoneWindowManager.DELAYED_KEYGUARD"),
)
runCurrent()
// It should not have any effect.
- assertEquals(
- listOf(
- false,
- true,
- false,
- true,
- ),
- canWake
- )
+ assertEquals(listOf(false, true, false, true), canWake)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt
index e60a52c12c0b..21019875f51e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt
@@ -55,6 +55,7 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
+import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
@@ -886,6 +887,7 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() {
@Test
@EnableSceneContainer
+ @Ignore("b/378766637")
fun lockscreenVisibilityWithScenes() =
testScope.runTest {
val isDeviceUnlocked by
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
index 63ec78fd9ee5..63ec78fd9ee5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
index 646722bee35f..2905a7329d21 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
@@ -116,7 +116,6 @@ import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
-import com.android.systemui.statusbar.data.repository.LightBarControllerStore;
import com.android.systemui.statusbar.phone.AutoHideController;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.LightBarController;
@@ -209,7 +208,7 @@ public class NavigationBarTest extends SysuiTestCase {
@Mock
private LightBarController mLightBarController;
@Mock
- private LightBarControllerStore mLightBarControllerStore;
+ private LightBarController.Factory mLightBarcontrollerFactory;
@Mock
private AutoHideController mAutoHideController;
@Mock
@@ -258,7 +257,7 @@ public class NavigationBarTest extends SysuiTestCase {
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
- when(mLightBarControllerStore.forDisplay(anyInt())).thenReturn(mLightBarController);
+ when(mLightBarcontrollerFactory.create(any(Context.class))).thenReturn(mLightBarController);
when(mAutoHideControllerFactory.create(any(Context.class))).thenReturn(mAutoHideController);
when(mNavigationBarView.getHomeButton()).thenReturn(mHomeButton);
when(mNavigationBarView.getRecentsButton()).thenReturn(mRecentsButton);
@@ -650,7 +649,8 @@ public class NavigationBarTest extends SysuiTestCase {
mFakeExecutor,
mUiEventLogger,
mNavBarHelper,
- mLightBarControllerStore,
+ mLightBarController,
+ mLightBarcontrollerFactory,
mAutoHideController,
mAutoHideControllerFactory,
Optional.of(mTelecomManager),
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModelTest.kt
new file mode 100644
index 000000000000..98770c724126
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModelTest.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.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.kosmos.testScope
+import com.android.systemui.qs.FakeQSTile
+import com.android.systemui.qs.pipeline.data.repository.tileSpecRepository
+import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class DetailsViewModelTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private lateinit var underTest: DetailsViewModel
+ private val spec = TileSpec.create("internet")
+ private val specNoDetails = TileSpec.create("NoDetailsTile")
+
+ @Before
+ fun setUp() {
+ underTest = kosmos.detailsViewModel
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun changeTileDetailsViewModel() = with(kosmos) {
+ testScope.runTest {
+ val specs = listOf(
+ spec,
+ specNoDetails,
+ )
+ tileSpecRepository.setTiles(0, specs)
+ runCurrent()
+
+ val tiles = currentTilesInteractor.currentTiles.value
+
+ assertThat(currentTilesInteractor.currentTilesSpecs.size).isEqualTo(2)
+ assertThat(tiles!![1].spec).isEqualTo(specNoDetails)
+ (tiles!![1].tile as FakeQSTile).hasDetailsViewModel = false
+
+ assertThat(underTest.activeTileDetails).isNull()
+
+ // Click on the tile who has the `spec`.
+ assertThat(underTest.onTileClicked(spec)).isTrue()
+ assertThat(underTest.activeTileDetails).isNotNull()
+ assertThat(underTest.activeTileDetails?.getTitle()).isEqualTo("internet")
+
+ // Click on a tile who dose not have a valid spec.
+ assertThat(underTest.onTileClicked(null)).isFalse()
+ assertThat(underTest.activeTileDetails).isNull()
+
+ // Click again on the tile who has the `spec`.
+ assertThat(underTest.onTileClicked(spec)).isTrue()
+ assertThat(underTest.activeTileDetails).isNotNull()
+ assertThat(underTest.activeTileDetails?.getTitle()).isEqualTo("internet")
+
+ // Click on a tile who dose not have a detailed view.
+ assertThat(underTest.onTileClicked(specNoDetails)).isFalse()
+ assertThat(underTest.activeTileDetails).isNull()
+
+ underTest.closeDetailedView()
+ assertThat(underTest.activeTileDetails).isNull()
+
+ assertThat(underTest.onTileClicked(null)).isFalse()
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
index 8995f46e7a72..165ff7bf08b1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
@@ -691,6 +691,32 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() {
verify(logger, never()).logTileUserChanged(TileSpec.create("a"), 0)
}
+
+ @Test
+ fun getTileDetails() =
+ testScope.runTest(USER_INFO_0) {
+ val tiles by collectLastValue(underTest.currentTiles)
+ val tileA = TileSpec.create("a")
+ val tileB = TileSpec.create("b")
+ val tileNoDetails = TileSpec.create("NoDetails")
+
+ val specs = listOf(tileA, tileB, tileNoDetails)
+
+ assertThat(tiles!!.isEmpty()).isTrue()
+
+ tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+ assertThat(tiles!!.size).isEqualTo(3)
+
+ // The third tile doesn't have a details view.
+ assertThat(tiles!![2].spec).isEqualTo(tileNoDetails)
+ (tiles!![2].tile as FakeQSTile).hasDetailsViewModel = false
+
+ assertThat(tiles!![0].tile.detailsViewModel.getTitle()).isEqualTo("a")
+ assertThat(tiles!![1].tile.detailsViewModel.getTitle()).isEqualTo("b")
+ assertThat(tiles!![2].tile.detailsViewModel).isNull()
+ }
+
+
private fun QSTile.State.fillIn(state: Int, label: CharSequence, secondaryLabel: CharSequence) {
this.state = state
this.label = label
@@ -770,7 +796,7 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() {
private val USER_INFO_0 = UserInfo().apply { id = 0 }
private val USER_INFO_1 = UserInfo().apply { id = 1 }
- private val VALID_TILES = setOf("a", "b", "c", "d", "e")
+ private val VALID_TILES = setOf("a", "b", "c", "d", "e", "NoDetails")
private val TEST_COMPONENT = ComponentName("pkg", "cls")
private val CUSTOM_TILE_SPEC = TileSpec.Companion.create(TEST_COMPONENT)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt
index db2297c99315..3d014b6822b4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt
@@ -35,9 +35,9 @@ import com.android.systemui.statusbar.NotificationPresenter
import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
import com.android.systemui.statusbar.notification.init.NotificationsController
import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
-import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 5d49c113a539..79bb0c401e78 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -2861,9 +2861,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
)
private fun fakeHeadsUpRowRepository(key: String, isPinned: Boolean) =
- FakeHeadsUpRowRepository(key = key, elementKey = Any()).apply {
- this.isPinned.value = isPinned
- }
+ FakeHeadsUpRowRepository(key = key, elementKey = Any(), isPinned = isPinned)
private fun setFingerprintSensorType(fingerprintSensorType: FingerprintSensorType) {
kosmos.fingerprintPropertyRepository.setProperties(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index ed31f366e1d0..0d8d57e52dbf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -150,7 +150,7 @@ import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.notification.ConversationNotificationManager;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
-import com.android.systemui.statusbar.notification.HeadsUpTouchHelper;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpTouchHelper;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinatorLogger;
import com.android.systemui.statusbar.notification.data.repository.NotificationsKeyguardViewStateRepository;
@@ -178,12 +178,12 @@ import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager;
+import com.android.systemui.statusbar.phone.ShadeTouchableRegionManager;
import com.android.systemui.statusbar.phone.TapAgainViewController;
import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
import com.android.systemui.statusbar.policy.CastController;
import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
import com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.KeyguardUserSwitcherController;
@@ -245,7 +245,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
@Mock protected NotificationPanelView mView;
@Mock protected LayoutInflater mLayoutInflater;
@Mock protected DynamicPrivacyController mDynamicPrivacyController;
- @Mock protected StatusBarTouchableRegionManager mStatusBarTouchableRegionManager;
+ @Mock protected ShadeTouchableRegionManager mShadeTouchableRegionManager;
@Mock protected KeyguardStateController mKeyguardStateController;
@Mock protected DozeLog mDozeLog;
private final ShadeLogger mShadeLog = new ShadeLogger(logcatLogBuffer());
@@ -703,7 +703,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
mMetricsLogger,
mShadeLog,
mConfigurationController,
- () -> flingAnimationUtilsBuilder, mStatusBarTouchableRegionManager,
+ () -> flingAnimationUtilsBuilder, mShadeTouchableRegionManager,
mConversationNotificationManager, mMediaHierarchyManager,
mStatusBarKeyguardViewManager,
mGutsManager,
@@ -819,7 +819,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
mLockscreenShadeTransitionController,
mNotificationShadeDepthController,
mShadeHeaderController,
- mStatusBarTouchableRegionManager,
+ mShadeTouchableRegionManager,
() -> mStatusBarLongPressGestureDetector,
mKeyguardStateController,
mKeyguardBypassController,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
index 61d4c9968d49..b58c13c34505 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
@@ -71,8 +71,8 @@ import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
import com.android.systemui.statusbar.phone.LightBarController;
import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
import com.android.systemui.statusbar.phone.ScrimController;
+import com.android.systemui.statusbar.phone.ShadeTouchableRegionManager;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager;
import com.android.systemui.statusbar.policy.CastController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
@@ -125,7 +125,7 @@ public class QuickSettingsControllerImplBaseTest extends SysuiTestCase {
@Mock protected LockscreenShadeTransitionController mLockscreenShadeTransitionController;
@Mock protected NotificationShadeDepthController mNotificationShadeDepthController;
@Mock protected ShadeHeaderController mShadeHeaderController;
- @Mock protected StatusBarTouchableRegionManager mStatusBarTouchableRegionManager;
+ @Mock protected ShadeTouchableRegionManager mShadeTouchableRegionManager;
@Mock protected StatusBarLongPressGestureDetector mStatusBarLongPressGestureDetector;
@Mock protected DozeParameters mDozeParameters;
@Mock protected KeyguardStateController mKeyguardStateController;
@@ -250,7 +250,7 @@ public class QuickSettingsControllerImplBaseTest extends SysuiTestCase {
mLockscreenShadeTransitionController,
mNotificationShadeDepthController,
mShadeHeaderController,
- mStatusBarTouchableRegionManager,
+ mShadeTouchableRegionManager,
() -> mStatusBarLongPressGestureDetector,
mKeyguardStateController,
mKeyguardBypassController,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
index 0f476d0a0455..c6ce58185cf0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
@@ -36,10 +36,10 @@ import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
import com.android.systemui.statusbar.notification.row.NotificationGutsManager
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.statusbar.policy.DeviceProvisionedController
-import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.statusbar.window.StatusBarWindowController
import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryTest.kt
new file mode 100644
index 000000000000..096675962d80
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryTest.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.data.repository
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.shade.display.ShadeDisplayPolicy
+import com.android.systemui.shade.display.SpecificDisplayIdPolicy
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ShadeDisplaysRepositoryTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val defaultPolicy = SpecificDisplayIdPolicy(0)
+
+ private val shadeDisplaysRepository =
+ ShadeDisplaysRepositoryImpl(defaultPolicy, testScope.backgroundScope)
+
+ @Test
+ fun policy_changing_propagatedFromTheLatestPolicy() =
+ testScope.runTest {
+ val displayIds by collectValues(shadeDisplaysRepository.displayId)
+ val policy1 = MutablePolicy()
+ val policy2 = MutablePolicy()
+
+ assertThat(displayIds).containsExactly(0)
+
+ shadeDisplaysRepository.policy.value = policy1
+
+ policy1.sendDisplayId(1)
+
+ assertThat(displayIds).containsExactly(0, 1)
+
+ policy1.sendDisplayId(2)
+
+ assertThat(displayIds).containsExactly(0, 1, 2)
+
+ shadeDisplaysRepository.policy.value = policy2
+
+ assertThat(displayIds).containsExactly(0, 1, 2, 0)
+
+ policy1.sendDisplayId(4)
+
+ // Changes to the first policy don't affect the output now
+ assertThat(displayIds).containsExactly(0, 1, 2, 0)
+
+ policy2.sendDisplayId(5)
+
+ assertThat(displayIds).containsExactly(0, 1, 2, 0, 5)
+ }
+
+ private class MutablePolicy : ShadeDisplayPolicy {
+ fun sendDisplayId(id: Int) {
+ _displayId.value = id
+ }
+
+ private val _displayId = MutableStateFlow(0)
+ override val name: String
+ get() = "mutable_policy"
+
+ override val displayId: StateFlow<Int>
+ get() = _displayId
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadePrimaryDisplayCommandTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadePrimaryDisplayCommandTest.kt
index af01547be7e3..d584dc9ceef2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadePrimaryDisplayCommandTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadePrimaryDisplayCommandTest.kt
@@ -23,12 +23,17 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.display.data.repository.displayRepository
import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.shade.ShadePrimaryDisplayCommand
+import com.android.systemui.shade.display.ShadeDisplayPolicy
import com.android.systemui.statusbar.commandline.commandRegistry
import com.android.systemui.testKosmos
+import com.google.common.truth.StringSubject
import com.google.common.truth.Truth.assertThat
import java.io.PrintWriter
import java.io.StringWriter
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -37,15 +42,26 @@ import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
class ShadePrimaryDisplayCommandTest : SysuiTestCase() {
- private val kosmos = testKosmos()
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
private val testScope = kosmos.testScope
private val commandRegistry = kosmos.commandRegistry
private val displayRepository = kosmos.displayRepository
- private val shadeDisplaysRepository = ShadeDisplaysRepositoryImpl()
+ private val defaultPolicy = kosmos.defaultShadeDisplayPolicy
+ private val policy1 = makePolicy("policy_1")
+ private val shadeDisplaysRepository = kosmos.shadeDisplaysRepository
private val pw = PrintWriter(StringWriter())
+ private val policies =
+ setOf(defaultPolicy, policy1, makePolicy("policy_2"), makePolicy("policy_3"))
+
private val underTest =
- ShadePrimaryDisplayCommand(commandRegistry, displayRepository, shadeDisplaysRepository)
+ ShadePrimaryDisplayCommand(
+ commandRegistry,
+ displayRepository,
+ shadeDisplaysRepository,
+ policies,
+ defaultPolicy,
+ )
@Before
fun setUp() {
@@ -96,4 +112,41 @@ class ShadePrimaryDisplayCommandTest : SysuiTestCase() {
assertThat(displayId).isEqualTo(newDisplayId)
}
+
+ @Test
+ fun policies_listsAllPolicies() =
+ testScope.runTest {
+ val stringWriter = StringWriter()
+ commandRegistry.onShellCommand(
+ PrintWriter(stringWriter),
+ arrayOf("shade_display_override", "policies"),
+ )
+ val result = stringWriter.toString()
+
+ assertThat(result).containsAllIn(policies.map { it.name })
+ }
+
+ @Test
+ fun policies_setsSpecificPolicy() =
+ testScope.runTest {
+ val policy by collectLastValue(shadeDisplaysRepository.policy)
+
+ commandRegistry.onShellCommand(pw, arrayOf("shade_display_override", policy1.name))
+
+ assertThat(policy!!.name).isEqualTo(policy1.name)
+ }
+
+ private fun makePolicy(policyName: String): ShadeDisplayPolicy {
+ return object : ShadeDisplayPolicy {
+ override val name: String
+ get() = policyName
+
+ override val displayId: StateFlow<Int>
+ get() = MutableStateFlow(0)
+ }
+ }
+}
+
+private fun StringSubject.containsAllIn(strings: List<String>) {
+ strings.forEach { contains(it) }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/display/AnyExternalShadeDisplayPolicyTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/display/AnyExternalShadeDisplayPolicyTest.kt
new file mode 100644
index 000000000000..4d4efd11f3a8
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/display/AnyExternalShadeDisplayPolicyTest.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.display
+
+import android.view.Display
+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.display.data.repository.display
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import kotlinx.coroutines.test.runTest
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AnyExternalShadeDisplayPolicyTest : SysuiTestCase() {
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+ private val testScope = kosmos.testScope
+ private val displayRepository = kosmos.displayRepository
+ val underTest = AnyExternalShadeDisplayPolicy(displayRepository, testScope.backgroundScope)
+
+ @Test
+ fun displayId_ignoresUnwantedTypes() =
+ testScope.runTest {
+ val displayId by collectLastValue(underTest.displayId)
+
+ displayRepository.addDisplays(
+ display(id = 0, type = Display.TYPE_INTERNAL),
+ display(id = 1, type = Display.TYPE_UNKNOWN),
+ display(id = 2, type = Display.TYPE_VIRTUAL),
+ display(id = 3, type = Display.TYPE_EXTERNAL),
+ )
+
+ assertThat(displayId).isEqualTo(3)
+ }
+
+ @Test
+ fun displayId_onceRemoved_goesToNextDisplay() =
+ testScope.runTest {
+ val displayId by collectLastValue(underTest.displayId)
+
+ displayRepository.addDisplays(
+ display(id = 0, type = Display.TYPE_INTERNAL),
+ display(id = 2, type = Display.TYPE_EXTERNAL),
+ display(id = 3, type = Display.TYPE_EXTERNAL),
+ )
+
+ assertThat(displayId).isEqualTo(2)
+
+ displayRepository.removeDisplay(2)
+
+ assertThat(displayId).isEqualTo(3)
+ }
+
+ @Test
+ fun displayId_onlyDefaultDisplay_defaultDisplay() =
+ testScope.runTest {
+ val displayId by collectLastValue(underTest.displayId)
+
+ displayRepository.addDisplays(display(id = 0, type = Display.TYPE_INTERNAL))
+
+ assertThat(displayId).isEqualTo(0)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorTest.kt
index 016a24acd461..982c51b8318c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorTest.kt
@@ -17,7 +17,6 @@
package com.android.systemui.shade.domain.interactor
import android.content.Context
-import android.content.MutableContextWrapper
import android.content.res.Configuration
import android.content.res.Resources
import android.view.Display
@@ -30,7 +29,6 @@ import com.android.systemui.display.data.repository.FakeDisplayWindowPropertiesR
import com.android.systemui.display.shared.model.DisplayWindowProperties
import com.android.systemui.scene.ui.view.WindowRootView
import com.android.systemui.shade.data.repository.FakeShadeDisplayRepository
-import com.android.systemui.statusbar.phone.ConfigurationForwarder
import java.util.Optional
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
@@ -39,6 +37,7 @@ import kotlinx.coroutines.test.advanceUntilIdle
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mockito.inOrder
import org.mockito.Mockito.verify
import org.mockito.kotlin.any
import org.mockito.kotlin.eq
@@ -53,13 +52,10 @@ class ShadeDisplaysInteractorTest : SysuiTestCase() {
private val shadeRootview = mock<WindowRootView>()
private val positionRepository = FakeShadeDisplayRepository()
- private val defaultContext = mock<Context>()
- private val secondaryContext = mock<Context>()
+ private val shadeContext = mock<Context>()
private val contextStore = FakeDisplayWindowPropertiesRepository()
private val testScope = TestScope(UnconfinedTestDispatcher())
- private val configurationForwarder = mock<ConfigurationForwarder>()
- private val defaultWm = mock<WindowManager>()
- private val secondaryWm = mock<WindowManager>()
+ private val shadeWm = mock<WindowManager>()
private val resources = mock<Resources>()
private val configuration = mock<Configuration>()
private val display = mock<Display>()
@@ -68,11 +64,9 @@ class ShadeDisplaysInteractorTest : SysuiTestCase() {
ShadeDisplaysInteractor(
Optional.of(shadeRootview),
positionRepository,
- MutableContextWrapper(defaultContext),
- resources,
- contextStore,
+ shadeContext,
+ shadeWm,
testScope.backgroundScope,
- configurationForwarder,
testScope.backgroundScope.coroutineContext,
)
@@ -83,28 +77,15 @@ class ShadeDisplaysInteractorTest : SysuiTestCase() {
whenever(resources.configuration).thenReturn(configuration)
- whenever(defaultContext.displayId).thenReturn(0)
- whenever(defaultContext.getSystemService(any())).thenReturn(defaultWm)
- whenever(defaultContext.resources).thenReturn(resources)
+ whenever(shadeContext.displayId).thenReturn(0)
+ whenever(shadeContext.getSystemService(any())).thenReturn(shadeWm)
+ whenever(shadeContext.resources).thenReturn(resources)
contextStore.insert(
DisplayWindowProperties(
displayId = 0,
windowType = TYPE_NOTIFICATION_SHADE,
- context = defaultContext,
- windowManager = defaultWm,
- layoutInflater = mock(),
- )
- )
-
- whenever(secondaryContext.displayId).thenReturn(1)
- whenever(secondaryContext.getSystemService(any())).thenReturn(secondaryWm)
- whenever(secondaryContext.resources).thenReturn(resources)
- contextStore.insert(
- DisplayWindowProperties(
- displayId = 1,
- windowType = TYPE_NOTIFICATION_SHADE,
- context = secondaryContext,
- windowManager = secondaryWm,
+ context = shadeContext,
+ windowManager = shadeWm,
layoutInflater = mock(),
)
)
@@ -117,8 +98,7 @@ class ShadeDisplaysInteractorTest : SysuiTestCase() {
interactor.start()
testScope.advanceUntilIdle()
- verifyNoMoreInteractions(defaultWm)
- verifyNoMoreInteractions(secondaryWm)
+ verifyNoMoreInteractions(shadeWm)
}
@Test
@@ -127,8 +107,10 @@ class ShadeDisplaysInteractorTest : SysuiTestCase() {
positionRepository.setDisplayId(1)
interactor.start()
- verify(defaultWm).removeView(eq(shadeRootview))
- verify(secondaryWm).addView(eq(shadeRootview), any())
+ inOrder(shadeWm).apply {
+ verify(shadeWm).removeView(eq(shadeRootview))
+ verify(shadeWm).addView(eq(shadeRootview), any())
+ }
}
@Test
@@ -139,18 +121,9 @@ class ShadeDisplaysInteractorTest : SysuiTestCase() {
positionRepository.setDisplayId(1)
- verify(defaultWm).removeView(eq(shadeRootview))
- verify(secondaryWm).addView(eq(shadeRootview), any())
- }
-
- @Test
- fun start_shadePositionChanges_newConfigPropagated() {
- whenever(display.displayId).thenReturn(0)
- positionRepository.setDisplayId(0)
- interactor.start()
-
- positionRepository.setDisplayId(1)
-
- verify(configurationForwarder).onConfigurationChanged(eq(configuration))
+ inOrder(shadeWm).apply {
+ verify(shadeWm).removeView(eq(shadeRootview))
+ verify(shadeWm).addView(eq(shadeRootview), any())
+ }
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModelTest.kt
index 558606f00300..a9d5790d1f08 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModelTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.shade.ui.viewmodel
+import android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS
import android.platform.test.annotations.DisableFlags
import android.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -42,6 +43,7 @@ import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.shade.shared.flag.DualShade
import com.android.systemui.shade.shared.model.ShadeMode
+import com.android.systemui.statusbar.disableflags.data.repository.fakeDisableFlagsRepository
import com.android.systemui.testKosmos
import com.android.systemui.unfold.fakeUnfoldTransitionProgressProvider
import com.google.common.truth.Truth.assertThat
@@ -49,6 +51,7 @@ import java.util.Locale
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.update
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -158,10 +161,7 @@ class ShadeSceneContentViewModelTest : SysuiTestCase() {
underTest.unfoldTranslationX(isOnStartSide = true),
underTest.unfoldTranslationX(isOnStartSide = false),
) { start, end ->
- Translations(
- start = start,
- end = end,
- )
+ Translations(start = start, end = end)
}
)
@@ -186,6 +186,20 @@ class ShadeSceneContentViewModelTest : SysuiTestCase() {
assertThat(translations?.end).isEqualTo(-0f)
}
+ @Test
+ fun disable2QuickSettings_isQsEnabledIsFalse() =
+ testScope.runTest {
+ val isQsEnabled by collectLastValue(underTest.isQsEnabled)
+ assertThat(isQsEnabled).isTrue()
+
+ kosmos.fakeDisableFlagsRepository.disableFlags.update {
+ it.copy(disable2 = DISABLE2_QUICK_SETTINGS)
+ }
+ runCurrent()
+
+ assertThat(isQsEnabled).isFalse()
+ }
+
private fun prepareConfiguration(): Int {
val configuration = context.resources.configuration
configuration.setLayoutDirection(Locale.US)
@@ -193,7 +207,7 @@ class ShadeSceneContentViewModelTest : SysuiTestCase() {
val maxTranslation = 10
kosmos.fakeConfigurationRepository.setDimensionPixelSize(
R.dimen.notification_side_paddings,
- maxTranslation
+ maxTranslation,
)
return maxTranslation
}
@@ -224,8 +238,5 @@ class ShadeSceneContentViewModelTest : SysuiTestCase() {
runCurrent()
}
- private data class Translations(
- val start: Float,
- val end: Float,
- )
+ private data class Translations(val start: Float, val end: Float)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginActionManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/plugins/PluginActionManagerTest.java
index 3ad0605f2781..3ad0605f2781 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/plugins/PluginActionManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/plugins/PluginActionManagerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index da0029ff6746..32a9f544ae17 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -48,7 +48,6 @@ import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyObject;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
@@ -1633,7 +1632,7 @@ public class KeyguardIndicationControllerTest extends KeyguardIndicationControll
} else {
verify(mRotateTextViewController).hideIndication(type);
verify(mRotateTextViewController, never()).updateIndication(eq(type),
- anyObject(), anyBoolean());
+ any(), anyBoolean());
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/PulseExpansionHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/PulseExpansionHandlerTest.kt
index 9907740672ed..cd66ef32180a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/PulseExpansionHandlerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/PulseExpansionHandlerTest.kt
@@ -26,10 +26,10 @@ import com.android.systemui.plugins.FalsingManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
import com.android.systemui.statusbar.notification.row.ExpandableView
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.util.mockito.mock
import kotlinx.coroutines.flow.MutableStateFlow
import org.junit.Before
@@ -76,7 +76,7 @@ class PulseExpansionHandlerTest : SysuiTestCase() {
falsingManager,
shadeInteractor,
lockscreenShadeTransitionController,
- dumpManager
+ dumpManager,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt
new file mode 100644
index 000000000000..7fed47a4653e
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.chips.notification.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.activity.data.repository.activityManagerRepository
+import com.android.systemui.activity.data.repository.fake
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SingleNotificationChipInteractorTest : SysuiTestCase() {
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+ val factory = kosmos.singleNotificationChipInteractorFactory
+
+ @Test
+ fun notificationChip_startsWithStartingModel() =
+ kosmos.runTest {
+ val icon = mock<StatusBarIconView>()
+ val startingNotif = activeNotificationModel(key = "notif1", statusBarChipIcon = icon)
+
+ val underTest = factory.create(startingNotif)
+
+ val latest by collectLastValue(underTest.notificationChip)
+
+ assertThat(latest!!.key).isEqualTo("notif1")
+ assertThat(latest!!.statusBarChipIconView).isEqualTo(icon)
+ }
+
+ @Test
+ fun notificationChip_updatesAfterSet() =
+ kosmos.runTest {
+ val originalIconView = mock<StatusBarIconView>()
+ val underTest =
+ factory.create(
+ activeNotificationModel(key = "notif1", statusBarChipIcon = originalIconView)
+ )
+
+ val latest by collectLastValue(underTest.notificationChip)
+
+ val newIconView = mock<StatusBarIconView>()
+ underTest.setNotification(
+ activeNotificationModel(key = "notif1", statusBarChipIcon = newIconView)
+ )
+
+ assertThat(latest!!.key).isEqualTo("notif1")
+ assertThat(latest!!.statusBarChipIconView).isEqualTo(newIconView)
+ }
+
+ @Test
+ fun notificationChip_ignoresSetWithDifferentKey() =
+ kosmos.runTest {
+ val originalIconView = mock<StatusBarIconView>()
+ val underTest =
+ factory.create(
+ activeNotificationModel(key = "notif1", statusBarChipIcon = originalIconView)
+ )
+
+ val latest by collectLastValue(underTest.notificationChip)
+
+ val newIconView = mock<StatusBarIconView>()
+ underTest.setNotification(
+ activeNotificationModel(key = "other_notif", statusBarChipIcon = newIconView)
+ )
+
+ assertThat(latest!!.key).isEqualTo("notif1")
+ assertThat(latest!!.statusBarChipIconView).isEqualTo(originalIconView)
+ }
+
+ @Test
+ fun notificationChip_missingStatusBarIconChipView_inConstructor_emitsNull() =
+ kosmos.runTest {
+ val underTest =
+ factory.create(activeNotificationModel(key = "notif1", statusBarChipIcon = null))
+
+ val latest by collectLastValue(underTest.notificationChip)
+
+ assertThat(latest).isNull()
+ }
+
+ @Test
+ fun notificationChip_missingStatusBarIconChipView_inSet_emitsNull() =
+ kosmos.runTest {
+ val startingNotif = activeNotificationModel(key = "notif1", statusBarChipIcon = mock())
+ val underTest = factory.create(startingNotif)
+ val latest by collectLastValue(underTest.notificationChip)
+ assertThat(latest).isNotNull()
+
+ underTest.setNotification(
+ activeNotificationModel(key = "notif1", statusBarChipIcon = null)
+ )
+
+ assertThat(latest).isNull()
+ }
+
+ @Test
+ fun notificationChip_appIsVisibleOnCreation_emitsNull() =
+ kosmos.runTest {
+ activityManagerRepository.fake.startingIsAppVisibleValue = true
+
+ val underTest =
+ factory.create(
+ activeNotificationModel(key = "notif", uid = UID, statusBarChipIcon = mock())
+ )
+
+ val latest by collectLastValue(underTest.notificationChip)
+
+ assertThat(latest).isNull()
+ }
+
+ @Test
+ fun notificationChip_appNotVisibleOnCreation_emitsValue() =
+ kosmos.runTest {
+ activityManagerRepository.fake.startingIsAppVisibleValue = false
+
+ val underTest =
+ factory.create(
+ activeNotificationModel(key = "notif", uid = UID, statusBarChipIcon = mock())
+ )
+
+ val latest by collectLastValue(underTest.notificationChip)
+
+ assertThat(latest).isNotNull()
+ }
+
+ @Test
+ fun notificationChip_hidesWhenAppIsVisible() =
+ kosmos.runTest {
+ val underTest =
+ factory.create(
+ activeNotificationModel(key = "notif", uid = UID, statusBarChipIcon = mock())
+ )
+
+ val latest by collectLastValue(underTest.notificationChip)
+
+ activityManagerRepository.fake.setIsAppVisible(UID, false)
+ assertThat(latest).isNotNull()
+
+ activityManagerRepository.fake.setIsAppVisible(UID, true)
+ assertThat(latest).isNull()
+
+ activityManagerRepository.fake.setIsAppVisible(UID, false)
+ assertThat(latest).isNotNull()
+ }
+
+ // Note: This test is theoretically impossible because the notification key should contain the
+ // UID, so if the UID changes then the key would also change and a new interactor would be
+ // created. But, test it just in case.
+ @Test
+ fun notificationChip_updatedUid_rechecksAppVisibility_oldObserverUnregistered() =
+ kosmos.runTest {
+ activityManagerRepository.fake.startingIsAppVisibleValue = false
+
+ val hiddenUid = 100
+ val shownUid = 101
+
+ val underTest =
+ factory.create(
+ activeNotificationModel(
+ key = "notif",
+ uid = hiddenUid,
+ statusBarChipIcon = mock(),
+ )
+ )
+ val latest by collectLastValue(underTest.notificationChip)
+ assertThat(latest).isNotNull()
+
+ // WHEN the notif gets a new UID that starts as visible
+ activityManagerRepository.fake.startingIsAppVisibleValue = true
+ underTest.setNotification(
+ activeNotificationModel(key = "notif", uid = shownUid, statusBarChipIcon = mock())
+ )
+
+ // THEN we re-fetch the app visibility state with the new UID, and since that UID is
+ // visible, we hide the chip
+ assertThat(latest).isNull()
+ }
+
+ companion object {
+ private const val UID = 885
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorTest.kt
index 19ed6a57d2f0..702e101d2d39 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorTest.kt
@@ -16,30 +16,277 @@
package com.android.systemui.statusbar.chips.notification.domain.interactor
+import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
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.coroutines.collectValues
import com.android.systemui.kosmos.testScope
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
+import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
+import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
+import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
import kotlinx.coroutines.test.runTest
import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
@SmallTest
@RunWith(AndroidJUnit4::class)
-@EnableFlags(StatusBarNotifChips.FLAG_NAME)
class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
private val kosmos = testKosmos().useUnconfinedTestDispatcher()
private val testScope = kosmos.testScope
+ private val activeNotificationListRepository = kosmos.activeNotificationListRepository
- private val underTest = kosmos.statusBarNotificationChipsInteractor
+ private val underTest by lazy {
+ kosmos.statusBarNotificationChipsInteractor.also { it.start() }
+ }
@Test
+ @DisableFlags(StatusBarNotifChips.FLAG_NAME)
+ fun notificationChips_flagOff_noNotifs() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.notificationChips)
+
+ setNotifs(
+ listOf(
+ activeNotificationModel(
+ key = "notif",
+ statusBarChipIcon = mock<StatusBarIconView>(),
+ isPromoted = true,
+ )
+ )
+ )
+
+ assertThat(latest).isEmpty()
+ }
+
+ @Test
+ @EnableFlags(StatusBarNotifChips.FLAG_NAME)
+ fun notificationChips_noNotifs_empty() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.notificationChips)
+
+ setNotifs(emptyList())
+
+ assertThat(latest).isEmpty()
+ }
+
+ @Test
+ @EnableFlags(StatusBarNotifChips.FLAG_NAME)
+ fun notificationChips_notifMissingStatusBarChipIconView_empty() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.notificationChips)
+
+ setNotifs(
+ listOf(
+ activeNotificationModel(
+ key = "notif",
+ statusBarChipIcon = null,
+ isPromoted = true,
+ )
+ )
+ )
+
+ assertThat(latest).isEmpty()
+ }
+
+ @Test
+ @EnableFlags(StatusBarNotifChips.FLAG_NAME)
+ fun notificationChips_onePromotedNotif_statusBarIconViewMatches() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.notificationChips)
+
+ val icon = mock<StatusBarIconView>()
+ setNotifs(
+ listOf(
+ activeNotificationModel(
+ key = "notif",
+ statusBarChipIcon = icon,
+ isPromoted = true,
+ )
+ )
+ )
+
+ assertThat(latest).hasSize(1)
+ assertThat(latest!![0].key).isEqualTo("notif")
+ assertThat(latest!![0].statusBarChipIconView).isEqualTo(icon)
+ }
+
+ @Test
+ @EnableFlags(StatusBarNotifChips.FLAG_NAME)
+ fun notificationChips_onlyForPromotedNotifs() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.notificationChips)
+
+ val firstIcon = mock<StatusBarIconView>()
+ val secondIcon = mock<StatusBarIconView>()
+ setNotifs(
+ listOf(
+ activeNotificationModel(
+ key = "notif1",
+ statusBarChipIcon = firstIcon,
+ isPromoted = true,
+ ),
+ activeNotificationModel(
+ key = "notif2",
+ statusBarChipIcon = secondIcon,
+ isPromoted = true,
+ ),
+ activeNotificationModel(
+ key = "notif3",
+ statusBarChipIcon = mock<StatusBarIconView>(),
+ isPromoted = false,
+ ),
+ )
+ )
+
+ assertThat(latest).hasSize(2)
+ assertThat(latest!![0].key).isEqualTo("notif1")
+ assertThat(latest!![0].statusBarChipIconView).isEqualTo(firstIcon)
+ assertThat(latest!![1].key).isEqualTo("notif2")
+ assertThat(latest!![1].statusBarChipIconView).isEqualTo(secondIcon)
+ }
+
+ @Test
+ @EnableFlags(StatusBarNotifChips.FLAG_NAME)
+ fun notificationChips_notifUpdatesGoThrough() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.notificationChips)
+
+ val firstIcon = mock<StatusBarIconView>()
+ val secondIcon = mock<StatusBarIconView>()
+ val thirdIcon = mock<StatusBarIconView>()
+
+ setNotifs(
+ listOf(
+ activeNotificationModel(
+ key = "notif",
+ statusBarChipIcon = firstIcon,
+ isPromoted = true,
+ )
+ )
+ )
+ assertThat(latest).hasSize(1)
+ assertThat(latest!![0].key).isEqualTo("notif")
+ assertThat(latest!![0].statusBarChipIconView).isEqualTo(firstIcon)
+
+ setNotifs(
+ listOf(
+ activeNotificationModel(
+ key = "notif",
+ statusBarChipIcon = secondIcon,
+ isPromoted = true,
+ )
+ )
+ )
+ assertThat(latest).hasSize(1)
+ assertThat(latest!![0].key).isEqualTo("notif")
+ assertThat(latest!![0].statusBarChipIconView).isEqualTo(secondIcon)
+
+ setNotifs(
+ listOf(
+ activeNotificationModel(
+ key = "notif",
+ statusBarChipIcon = thirdIcon,
+ isPromoted = true,
+ )
+ )
+ )
+ assertThat(latest).hasSize(1)
+ assertThat(latest!![0].key).isEqualTo("notif")
+ assertThat(latest!![0].statusBarChipIconView).isEqualTo(thirdIcon)
+ }
+
+ @Test
+ @EnableFlags(StatusBarNotifChips.FLAG_NAME)
+ fun notificationChips_promotedNotifDisappearsThenReappears() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.notificationChips)
+
+ setNotifs(
+ listOf(
+ activeNotificationModel(
+ key = "notif",
+ statusBarChipIcon = mock(),
+ isPromoted = true,
+ )
+ )
+ )
+ assertThat(latest).hasSize(1)
+ assertThat(latest!![0].key).isEqualTo("notif")
+
+ setNotifs(
+ listOf(
+ activeNotificationModel(
+ key = "notif",
+ statusBarChipIcon = mock(),
+ isPromoted = false,
+ )
+ )
+ )
+ assertThat(latest).isEmpty()
+
+ setNotifs(
+ listOf(
+ activeNotificationModel(
+ key = "notif",
+ statusBarChipIcon = mock(),
+ isPromoted = true,
+ )
+ )
+ )
+ assertThat(latest).hasSize(1)
+ assertThat(latest!![0].key).isEqualTo("notif")
+ }
+
+ @Test
+ @EnableFlags(StatusBarNotifChips.FLAG_NAME)
+ fun notificationChips_notifChangesKey() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.notificationChips)
+
+ val firstIcon = mock<StatusBarIconView>()
+ val secondIcon = mock<StatusBarIconView>()
+ setNotifs(
+ listOf(
+ activeNotificationModel(
+ key = "notif|uid1",
+ statusBarChipIcon = firstIcon,
+ isPromoted = true,
+ )
+ )
+ )
+ assertThat(latest).hasSize(1)
+ assertThat(latest!![0].key).isEqualTo("notif|uid1")
+ assertThat(latest!![0].statusBarChipIconView).isEqualTo(firstIcon)
+
+ // WHEN a notification changes UID, which is a key change
+ setNotifs(
+ listOf(
+ activeNotificationModel(
+ key = "notif|uid2",
+ statusBarChipIcon = secondIcon,
+ isPromoted = true,
+ )
+ )
+ )
+
+ // THEN we correctly update
+ assertThat(latest).hasSize(1)
+ assertThat(latest!![0].key).isEqualTo("notif|uid2")
+ assertThat(latest!![0].statusBarChipIconView).isEqualTo(secondIcon)
+ }
+
+ @Test
+ @EnableFlags(StatusBarNotifChips.FLAG_NAME)
fun onPromotedNotificationChipTapped_emitsKeys() =
testScope.runTest {
val latest by collectValues(underTest.promotedNotificationChipTapEvent)
@@ -56,6 +303,7 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(StatusBarNotifChips.FLAG_NAME)
fun onPromotedNotificationChipTapped_sameKeyTwice_emitsTwice() =
testScope.runTest {
val latest by collectValues(underTest.promotedNotificationChipTapEvent)
@@ -67,4 +315,11 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
assertThat(latest[0]).isEqualTo("fakeKey")
assertThat(latest[1]).isEqualTo("fakeKey")
}
+
+ private fun setNotifs(notifs: List<ActiveNotificationModel>) {
+ activeNotificationListRepository.activeNotifications.value =
+ ActiveNotificationsStore.Builder()
+ .apply { notifs.forEach { addIndividualNotif(it) } }
+ .build()
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
index 1b4132910555..16376c5b3850 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
@@ -22,7 +22,9 @@ 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.kosmos.testScope
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.chips.notification.domain.interactor.statusBarNotificationChipsInteractor
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
@@ -34,26 +36,28 @@ import com.android.systemui.statusbar.notification.shared.ActiveNotificationMode
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
+import org.junit.Before
import org.junit.runner.RunWith
import org.mockito.kotlin.mock
@SmallTest
@RunWith(AndroidJUnit4::class)
-@OptIn(ExperimentalCoroutinesApi::class)
@EnableFlags(StatusBarNotifChips.FLAG_NAME)
class NotifChipsViewModelTest : SysuiTestCase() {
- private val kosmos = testKosmos()
- private val testScope = kosmos.testScope
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
private val activeNotificationListRepository = kosmos.activeNotificationListRepository
- private val underTest = kosmos.notifChipsViewModel
+ private val underTest by lazy { kosmos.notifChipsViewModel }
+
+ @Before
+ fun setUp() {
+ kosmos.statusBarNotificationChipsInteractor.start()
+ }
@Test
fun chips_noNotifs_empty() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.chips)
setNotifs(emptyList())
@@ -63,7 +67,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
@Test
fun chips_notifMissingStatusBarChipIconView_empty() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.chips)
setNotifs(
@@ -81,7 +85,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
@Test
fun chips_onePromotedNotif_statusBarIconViewMatches() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.chips)
val icon = mock<StatusBarIconView>()
@@ -103,7 +107,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
@Test
fun chips_onlyForPromotedNotifs() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.chips)
val firstIcon = mock<StatusBarIconView>()
@@ -135,7 +139,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
@Test
fun chips_clickingChipNotifiesInteractor() =
- testScope.runTest {
+ kosmos.runTest {
val latest by collectLastValue(underTest.chips)
val latestChipTap by
collectLastValue(
@@ -163,7 +167,6 @@ class NotifChipsViewModelTest : SysuiTestCase() {
ActiveNotificationsStore.Builder()
.apply { notifs.forEach { addIndividualNotif(it) } }
.build()
- testScope.runCurrent()
}
companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
index 25d5ce50e03f..eb0978eff24b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
@@ -27,6 +27,7 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.mediaprojection.data.model.MediaProjectionState
import com.android.systemui.mediaprojection.data.repository.fakeMediaProjectionRepository
import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask
@@ -38,6 +39,7 @@ import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.Me
import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.setUpPackageManagerForMediaProjection
import com.android.systemui.statusbar.chips.notification.demo.ui.viewmodel.DemoNotifChipViewModelTest.Companion.addDemoNotifChip
import com.android.systemui.statusbar.chips.notification.demo.ui.viewmodel.demoNotifChipViewModel
+import com.android.systemui.statusbar.chips.notification.domain.interactor.statusBarNotificationChipsInteractor
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import com.android.systemui.statusbar.chips.notification.ui.viewmodel.NotifChipsViewModelTest.Companion.assertIsNotifChip
import com.android.systemui.statusbar.chips.ui.model.MultipleOngoingActivityChipsModel
@@ -67,6 +69,7 @@ import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
+import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.any
@@ -79,7 +82,7 @@ import org.mockito.kotlin.whenever
@OptIn(ExperimentalCoroutinesApi::class)
@EnableFlags(StatusBarNotifChips.FLAG_NAME)
class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
- private val kosmos = testKosmos()
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
private val testScope = kosmos.testScope
private val systemClock = kosmos.fakeSystemClock
private val commandRegistry = kosmos.commandRegistry
@@ -103,12 +106,13 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
.thenReturn(chipBackgroundView)
}
- private val underTest = kosmos.ongoingActivityChipsViewModel
+ private val underTest by lazy { kosmos.ongoingActivityChipsViewModel }
@Before
fun setUp() {
setUpPackageManagerForMediaProjection(kosmos)
kosmos.demoNotifChipViewModel.start()
+ kosmos.statusBarNotificationChipsInteractor.start()
val icon =
BitmapDrawable(
context.resources,
@@ -616,6 +620,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
}
@Test
+ @Ignore("b/364653005") // We'll need to re-do the animation story when we implement RON chips
fun primaryChip_screenRecordStoppedViaDialog_chipHiddenWithoutAnimation() =
testScope.runTest {
screenRecordState.value = ScreenRecordModel.Recording
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorControllerTest.kt
index cb92b7745961..a1772e3f62ed 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorControllerTest.kt
@@ -14,11 +14,11 @@ import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
import com.android.systemui.statusbar.notification.data.repository.NotificationLaunchAnimationRepository
import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
+import com.android.systemui.statusbar.notification.headsup.HeadsUpUtil
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.NotificationTestHelper
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
-import com.android.systemui.statusbar.policy.HeadsUpManager
-import com.android.systemui.statusbar.policy.HeadsUpUtil
import com.android.systemui.testKosmos
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
@@ -69,7 +69,7 @@ class NotificationTransitionAnimatorControllerTest : SysuiTestCase() {
headsUpManager,
notification,
kosmos.interactionJankMonitor,
- onFinishAnimationCallback
+ onFinishAnimationCallback,
)
}
@@ -95,7 +95,7 @@ class NotificationTransitionAnimatorControllerTest : SysuiTestCase() {
notificationKey,
/* releaseImmediately= */ true,
/* animate= */ true,
- /* reason= */ "onIntentStarted(willAnimate=false)"
+ /* reason= */ "onIntentStarted(willAnimate=false)",
)
verify(onFinishAnimationCallback).run()
}
@@ -118,7 +118,7 @@ class NotificationTransitionAnimatorControllerTest : SysuiTestCase() {
notificationKey,
/* releaseImmediately= */ true,
/* animate= */ true,
- /* reason= */ "onLaunchAnimationCancelled()"
+ /* reason= */ "onLaunchAnimationCancelled()",
)
verify(onFinishAnimationCallback).run()
}
@@ -141,7 +141,7 @@ class NotificationTransitionAnimatorControllerTest : SysuiTestCase() {
notificationKey,
/* releaseImmediately= */ true,
/* animate= */ false,
- /* reason= */ "onLaunchAnimationEnd()"
+ /* reason= */ "onLaunchAnimationEnd()",
)
verify(onFinishAnimationCallback).run()
}
@@ -180,14 +180,14 @@ class NotificationTransitionAnimatorControllerTest : SysuiTestCase() {
summary.key,
/* releaseImmediately= */ true,
/* animate= */ false,
- /* reason= */ "onLaunchAnimationEnd()"
+ /* reason= */ "onLaunchAnimationEnd()",
)
verify(headsUpManager, never())
.removeNotification(
notification.entry.key,
/* releaseImmediately= */ true,
/* animate= */ false,
- /* reason= */ "onLaunchAnimationEnd()"
+ /* reason= */ "onLaunchAnimationEnd()",
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
index c4b1b841c6a5..0dc01a65df19 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
@@ -39,13 +39,13 @@ import com.android.systemui.scene.data.repository.setSceneTransition
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.ShadeViewController.Companion.WAKEUP_ANIMATION_DELAY_MS
import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_WAKEUP
import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationsKeyguardInteractor
import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.phone.ScreenOffAnimationController
-import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.withArgCaptor
import com.google.common.truth.Truth.assertThat
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
index 2c488e3a7242..1d7f25784327 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
@@ -38,12 +38,13 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.OnBefo
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
import com.android.systemui.statusbar.notification.collection.mockNotifCollection
-import com.android.systemui.statusbar.notification.collection.notifCollection
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender.OnEndLifetimeExtensionCallback
import com.android.systemui.statusbar.notification.collection.provider.LaunchFullScreenIntentProvider
import com.android.systemui.statusbar.notification.collection.render.NodeController
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManagerImpl
+import com.android.systemui.statusbar.notification.headsup.OnHeadsUpChangedListener
import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderWrapper.DecisionImpl
@@ -51,8 +52,6 @@ import com.android.systemui.statusbar.notification.interruption.NotificationInte
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider
import com.android.systemui.statusbar.notification.row.NotifBindPipeline.BindCallback
import com.android.systemui.statusbar.phone.NotificationGroupTestHelper
-import com.android.systemui.statusbar.policy.BaseHeadsUpManager
-import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener
import com.android.systemui.testKosmos
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
@@ -84,7 +83,9 @@ import org.mockito.MockitoAnnotations
class HeadsUpCoordinatorTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val statusBarNotificationChipsInteractor = kosmos.statusBarNotificationChipsInteractor
+ private val statusBarNotificationChipsInteractor by lazy {
+ kosmos.statusBarNotificationChipsInteractor
+ }
private val notifCollection = kosmos.mockNotifCollection
private lateinit var coordinator: HeadsUpCoordinator
@@ -101,7 +102,7 @@ class HeadsUpCoordinatorTest : SysuiTestCase() {
private val notifPipeline: NotifPipeline = mock()
private val logger = HeadsUpCoordinatorLogger(logcatLogBuffer(), verbose = true)
- private val headsUpManager: BaseHeadsUpManager = mock()
+ private val headsUpManager: HeadsUpManagerImpl = mock()
private val headsUpViewBinder: HeadsUpViewBinder = mock()
private val visualInterruptionDecisionProvider: VisualInterruptionDecisionProvider = mock()
private val remoteInputManager: NotificationRemoteInputManager = mock()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorTest.kt
index d772e3effbeb..14148cdc0f03 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorTest.kt
@@ -21,7 +21,6 @@ import android.app.Notification
import android.app.NotificationManager.IMPORTANCE_DEFAULT
import android.app.NotificationManager.IMPORTANCE_LOW
import android.platform.test.annotations.EnableFlags
-import android.provider.Settings
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -43,6 +42,7 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.No
import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRowRepository
import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
import com.android.systemui.statusbar.notification.domain.interactor.lockScreenNotificationMinimalismSetting
+import com.android.systemui.statusbar.notification.headsup.PinnedStatus
import com.android.systemui.statusbar.notification.shared.NotificationMinimalism
import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository
import com.android.systemui.testKosmos
@@ -394,7 +394,7 @@ class LockScreenMinimalismCoordinatorTest : SysuiTestCase() {
assertThatTopUnseenKey().isEqualTo(solo1.key)
// TEST: even being pinned doesn't take effect immediately
- hunRepo1.isPinned.value = true
+ hunRepo1.pinnedStatus.value = PinnedStatus.PinnedBySystem
testScheduler.advanceTimeBy(0.5.seconds)
onBeforeTransformGroupsListener.onBeforeTransformGroups(listEntryList)
assertThatTopUnseenKey().isEqualTo(solo1.key)
@@ -406,8 +406,8 @@ class LockScreenMinimalismCoordinatorTest : SysuiTestCase() {
// TEST: repeat; being heads up and pinned for 1 second triggers seen
kosmos.headsUpNotificationRepository.orderedHeadsUpRows.value = listOf(hunRepo2)
- hunRepo1.isPinned.value = false
- hunRepo2.isPinned.value = true
+ hunRepo1.pinnedStatus.value = PinnedStatus.NotPinned
+ hunRepo2.pinnedStatus.value = PinnedStatus.PinnedBySystem
testScheduler.advanceTimeBy(1.seconds)
onBeforeTransformGroupsListener.onBeforeTransformGroups(listEntryList)
assertThatTopUnseenKey().isEqualTo(null)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt
index 3fd9c2160ce2..d38fb5050c5f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinatorTest.kt
@@ -27,7 +27,6 @@ import com.android.systemui.flags.andSceneContainer
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.keyguardRepository
-import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -48,9 +47,9 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.No
import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.lockScreenShowOnlyUnseenNotificationsSetting
import com.android.systemui.statusbar.notification.domain.interactor.seenNotificationsInteractor
+import com.android.systemui.statusbar.notification.headsup.OnHeadsUpChangedListener
+import com.android.systemui.statusbar.notification.headsup.headsUpManager
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
-import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener
-import com.android.systemui.statusbar.policy.headsUpManager
import com.android.systemui.testKosmos
import com.android.systemui.util.settings.FakeSettings
import com.android.systemui.util.settings.fakeSettings
@@ -155,7 +154,7 @@ class OriginalUnseenKeyguardCoordinatorTest(flags: FlagsParameterization) : Sysu
runKeyguardCoordinatorTest {
kosmos.setTransition(
sceneTransition = Idle(Scenes.Gone),
- stateTransition = TransitionStep(KeyguardState.LOCKSCREEN, KeyguardState.GONE)
+ stateTransition = TransitionStep(KeyguardState.LOCKSCREEN, KeyguardState.GONE),
)
// WHEN: A notification is posted
@@ -170,7 +169,7 @@ class OriginalUnseenKeyguardCoordinatorTest(flags: FlagsParameterization) : Sysu
keyguardRepository.setKeyguardShowing(true)
kosmos.setTransition(
sceneTransition = Idle(Scenes.Lockscreen),
- stateTransition = TransitionStep(KeyguardState.GONE, KeyguardState.AOD)
+ stateTransition = TransitionStep(KeyguardState.GONE, KeyguardState.AOD),
)
// THEN: The notification is recognized as "seen" and is filtered out.
@@ -180,7 +179,7 @@ class OriginalUnseenKeyguardCoordinatorTest(flags: FlagsParameterization) : Sysu
keyguardRepository.setKeyguardShowing(false)
kosmos.setTransition(
sceneTransition = Idle(Scenes.Gone),
- stateTransition = TransitionStep(KeyguardState.AOD, KeyguardState.GONE)
+ stateTransition = TransitionStep(KeyguardState.AOD, KeyguardState.GONE),
)
// THEN: The notification is shown regardless
@@ -359,14 +358,14 @@ class OriginalUnseenKeyguardCoordinatorTest(flags: FlagsParameterization) : Sysu
keyguardRepository.setKeyguardShowing(false)
kosmos.setTransition(
sceneTransition = Idle(Scenes.Gone),
- stateTransition = TransitionStep(KeyguardState.LOCKSCREEN, KeyguardState.GONE)
+ stateTransition = TransitionStep(KeyguardState.LOCKSCREEN, KeyguardState.GONE),
)
// WHEN: Keyguard is shown again
keyguardRepository.setKeyguardShowing(true)
kosmos.setTransition(
sceneTransition = Idle(Scenes.Lockscreen),
- stateTransition = TransitionStep(KeyguardState.GONE, KeyguardState.AOD)
+ stateTransition = TransitionStep(KeyguardState.GONE, KeyguardState.AOD),
)
// THEN: The notification is now recognized as "seen" and is filtered out.
@@ -412,7 +411,7 @@ class OriginalUnseenKeyguardCoordinatorTest(flags: FlagsParameterization) : Sysu
runKeyguardCoordinatorTest {
kosmos.setTransition(
sceneTransition = Idle(Scenes.Lockscreen),
- stateTransition = TransitionStep(KeyguardState.GONE, KeyguardState.LOCKSCREEN)
+ stateTransition = TransitionStep(KeyguardState.GONE, KeyguardState.LOCKSCREEN),
)
val firstEntry = NotificationEntryBuilder().setId(1).build()
collectionListener.onEntryAdded(firstEntry)
@@ -435,14 +434,14 @@ class OriginalUnseenKeyguardCoordinatorTest(flags: FlagsParameterization) : Sysu
keyguardRepository.setKeyguardShowing(false)
kosmos.setTransition(
sceneTransition = Idle(Scenes.Gone),
- stateTransition = TransitionStep(KeyguardState.LOCKSCREEN, KeyguardState.GONE)
+ stateTransition = TransitionStep(KeyguardState.LOCKSCREEN, KeyguardState.GONE),
)
// WHEN: Keyguard is shown again
keyguardRepository.setKeyguardShowing(true)
kosmos.setTransition(
sceneTransition = Idle(Scenes.Lockscreen),
- stateTransition = TransitionStep(KeyguardState.GONE, KeyguardState.LOCKSCREEN)
+ stateTransition = TransitionStep(KeyguardState.GONE, KeyguardState.LOCKSCREEN),
)
// THEN: The first notification is considered seen and is filtered out.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
index 3ad41a54ac7e..ba85e32484df 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
@@ -67,7 +67,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable;
import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.time.FakeSystemClock;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
index d717fe4c1e04..dc0231f40609 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
@@ -31,6 +31,7 @@ import com.android.systemui.kosmos.testScope
import com.android.systemui.shade.shadeTestUtil
import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRowRepository
import com.android.systemui.statusbar.notification.data.repository.notificationsKeyguardViewStateRepository
+import com.android.systemui.statusbar.notification.headsup.PinnedStatus
import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository
import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
import com.android.systemui.testKosmos
@@ -102,7 +103,7 @@ class HeadsUpNotificationInteractorTest : SysuiTestCase() {
}
@Test
- fun hasPinnedRows_rowGetsPinned_true() =
+ fun hasPinnedRows_rowGetsPinnedNormally_true() =
testScope.runTest {
val hasPinnedRows by collectLastValue(underTest.hasPinnedRows)
// GIVEN no rows are pinned
@@ -115,8 +116,30 @@ class HeadsUpNotificationInteractorTest : SysuiTestCase() {
headsUpRepository.setNotifications(rows)
runCurrent()
- // WHEN a row gets pinned
- rows[0].isPinned.value = true
+ // WHEN a row gets pinned normally
+ rows[0].pinnedStatus.value = PinnedStatus.PinnedBySystem
+ runCurrent()
+
+ // THEN hasPinnedRows updates to true
+ assertThat(hasPinnedRows).isTrue()
+ }
+
+ @Test
+ fun hasPinnedRows_rowGetsPinnedByUser_true() =
+ testScope.runTest {
+ val hasPinnedRows by collectLastValue(underTest.hasPinnedRows)
+ // GIVEN no rows are pinned
+ val rows =
+ arrayListOf(
+ fakeHeadsUpRowRepository("key 0"),
+ fakeHeadsUpRowRepository("key 1"),
+ fakeHeadsUpRowRepository("key 2"),
+ )
+ headsUpRepository.setNotifications(rows)
+ runCurrent()
+
+ // WHEN a row gets pinned due to a chip tap
+ rows[0].pinnedStatus.value = PinnedStatus.PinnedByUser
runCurrent()
// THEN hasPinnedRows updates to true
@@ -138,7 +161,7 @@ class HeadsUpNotificationInteractorTest : SysuiTestCase() {
runCurrent()
// THEN that row gets unpinned
- rows[0].isPinned.value = false
+ rows[0].pinnedStatus.value = PinnedStatus.NotPinned
runCurrent()
// THEN hasPinnedRows updates to false
@@ -246,7 +269,7 @@ class HeadsUpNotificationInteractorTest : SysuiTestCase() {
runCurrent()
// WHEN all rows gets pinned
- rows[2].isPinned.value = true
+ rows[2].pinnedStatus.value = PinnedStatus.PinnedBySystem
runCurrent()
// THEN no rows are filtered
@@ -271,7 +294,7 @@ class HeadsUpNotificationInteractorTest : SysuiTestCase() {
assertThat(activeHeadsUpRows).containsExactly(rows[0], rows[1], rows[2])
// WHEN all rows gets pinned
- rows[2].isPinned.value = true
+ rows[2].pinnedStatus.value = PinnedStatus.PinnedBySystem
runCurrent()
// THEN no change
@@ -329,7 +352,7 @@ class HeadsUpNotificationInteractorTest : SysuiTestCase() {
runCurrent()
// WHEN a row gets unpinned
- rows[0].isPinned.value = false
+ rows[0].pinnedStatus.value = PinnedStatus.NotPinned
runCurrent()
// THEN the unpinned row is filtered
@@ -351,7 +374,7 @@ class HeadsUpNotificationInteractorTest : SysuiTestCase() {
runCurrent()
// WHEN a row gets unpinned
- rows[0].isPinned.value = false
+ rows[0].pinnedStatus.value = PinnedStatus.NotPinned
runCurrent()
// THEN all rows are still present
@@ -372,15 +395,15 @@ class HeadsUpNotificationInteractorTest : SysuiTestCase() {
headsUpRepository.setNotifications(rows)
runCurrent()
- rows[0].isPinned.value = true
+ rows[0].pinnedStatus.value = PinnedStatus.PinnedBySystem
runCurrent()
assertThat(pinnedHeadsUpRows).containsExactly(rows[0])
- rows[0].isPinned.value = false
+ rows[0].pinnedStatus.value = PinnedStatus.NotPinned
runCurrent()
assertThat(pinnedHeadsUpRows).isEmpty()
- rows[0].isPinned.value = true
+ rows[0].pinnedStatus.value = PinnedStatus.PinnedBySystem
runCurrent()
assertThat(pinnedHeadsUpRows).containsExactly(rows[0])
}
@@ -485,7 +508,5 @@ class HeadsUpNotificationInteractorTest : SysuiTestCase() {
}
private fun fakeHeadsUpRowRepository(key: String, isPinned: Boolean = false) =
- FakeHeadsUpRowRepository(key = key, elementKey = Any()).apply {
- this.isPinned.value = isPinned
- }
+ FakeHeadsUpRowRepository(key = key, isPinned = isPinned)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/AvalancheControllerTest.kt
index c5eed7365d92..22a9c64d2cc9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/AvalancheControllerTest.kt
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.systemui.statusbar.policy
+package com.android.systemui.statusbar.notification.headsup
import android.app.Notification
import android.os.Handler
@@ -31,9 +31,11 @@ import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
import com.android.systemui.statusbar.notification.collection.provider.visualStabilityProvider
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManagerImpl
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManagerTestUtil.createFullScreenIntentEntry
import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
import com.android.systemui.statusbar.phone.keyguardBypassController
-import com.android.systemui.statusbar.policy.HeadsUpManagerTestUtil.createFullScreenIntentEntry
+import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper
+import com.android.systemui.statusbar.policy.configurationController
import com.android.systemui.testKosmos
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.kotlin.JavaAdapter
@@ -76,7 +78,7 @@ class AvalancheControllerTest : SysuiTestCase() {
private val mGlobalSettings = FakeGlobalSettings()
private val mSystemClock = FakeSystemClock()
private val mExecutor = FakeExecutor(mSystemClock)
- private lateinit var testableHeadsUpManager: BaseHeadsUpManager
+ private lateinit var testableHeadsUpManager: HeadsUpManagerImpl
@Before
fun setUp() {
@@ -114,7 +116,7 @@ class AvalancheControllerTest : SysuiTestCase() {
)
}
- private fun createHeadsUpEntry(id: Int): BaseHeadsUpManager.HeadsUpEntry {
+ private fun createHeadsUpEntry(id: Int): HeadsUpManagerImpl.HeadsUpEntry {
return testableHeadsUpManager.createHeadsUpEntry(
NotificationEntryBuilder()
.setSbn(HeadsUpManagerTestUtil.createSbn(id, Notification.Builder(mContext, "")))
@@ -122,7 +124,7 @@ class AvalancheControllerTest : SysuiTestCase() {
)
}
- private fun createFsiHeadsUpEntry(id: Int): BaseHeadsUpManager.HeadsUpEntry {
+ private fun createFsiHeadsUpEntry(id: Int): HeadsUpManagerImpl.HeadsUpEntry {
return testableHeadsUpManager.createHeadsUpEntry(createFullScreenIntentEntry(id, mContext))
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplTest.java
index 0fbee6d29441..01f78cb289fd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.policy;
+package com.android.systemui.statusbar.notification.headsup;
import static android.app.Notification.FLAG_FSI_REQUESTED_BUT_DENIED;
@@ -54,18 +54,17 @@ import com.android.systemui.dump.DumpManager;
import com.android.systemui.kosmos.KosmosJavaAdapter;
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.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManagerImpl;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun;
+import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.settings.FakeGlobalSettings;
import com.android.systemui.util.time.FakeSystemClock;
-import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -73,6 +72,7 @@ import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
+import kotlinx.coroutines.flow.StateFlowKt;
import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
import platform.test.runner.parameterized.Parameters;
@@ -81,8 +81,8 @@ import java.util.List;
@SmallTest
@TestableLooper.RunWithLooper
@RunWith(ParameterizedAndroidJunit4.class)
-// TODO(b/378142453): Merge this with BaseHeadsUpManagerTest.
-public class BaseHeadsUpManagerTest extends SysuiTestCase {
+// TODO(b/378142453): Merge this with HeadsUpManagerPhoneTest.
+public class HeadsUpManagerImplTest extends SysuiTestCase {
protected KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
@Rule
@@ -119,7 +119,7 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
assertThat(TEST_STICKY_AUTO_DISMISS_TIME).isLessThan(TEST_A11Y_AUTO_DISMISS_TIME);
}
- private BaseHeadsUpManager createHeadsUpManager() {
+ private HeadsUpManagerImpl createHeadsUpManager() {
return new TestableHeadsUpManager(
mContext,
mLogger,
@@ -169,7 +169,7 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
return FlagsParameterization.allCombinationsOf(NotificationThrottleHun.FLAG_NAME);
}
- public BaseHeadsUpManagerTest(FlagsParameterization flags) {
+ public HeadsUpManagerImplTest(FlagsParameterization flags) {
mSetFlagsRule.setFlagsParameterization(flags);
}
@@ -184,7 +184,7 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
@Test
public void testHasNotifications_headsUpManagerMapNotEmpty_true() {
- final BaseHeadsUpManager bhum = createHeadsUpManager();
+ final HeadsUpManagerImpl bhum = createHeadsUpManager();
final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
bhum.showNotification(entry);
@@ -195,10 +195,10 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
@Test
@EnableFlags(NotificationThrottleHun.FLAG_NAME)
public void testHasNotifications_avalancheMapNotEmpty_true() {
- final BaseHeadsUpManager bhum = createHeadsUpManager();
+ final HeadsUpManagerImpl bhum = createHeadsUpManager();
final NotificationEntry notifEntry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0,
mContext);
- final BaseHeadsUpManager.HeadsUpEntry headsUpEntry = bhum.createHeadsUpEntry(notifEntry);
+ final HeadsUpManagerImpl.HeadsUpEntry headsUpEntry = bhum.createHeadsUpEntry(notifEntry);
mAvalancheController.addToNext(headsUpEntry, () -> {});
assertThat(mAvalancheController.getWaitingEntryList()).isNotEmpty();
@@ -208,7 +208,7 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
@Test
@EnableFlags(NotificationThrottleHun.FLAG_NAME)
public void testHasNotifications_false() {
- final BaseHeadsUpManager bhum = createHeadsUpManager();
+ final HeadsUpManagerImpl bhum = createHeadsUpManager();
assertThat(bhum.mHeadsUpEntryMap).isEmpty();
assertThat(mAvalancheController.getWaitingEntryList()).isEmpty();
assertThat(bhum.hasNotifications()).isFalse();
@@ -217,10 +217,10 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
@Test
@EnableFlags(NotificationThrottleHun.FLAG_NAME)
public void testGetHeadsUpEntryList_includesAvalancheEntryList() {
- final BaseHeadsUpManager bhum = createHeadsUpManager();
+ final HeadsUpManagerImpl bhum = createHeadsUpManager();
final NotificationEntry notifEntry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0,
mContext);
- final BaseHeadsUpManager.HeadsUpEntry headsUpEntry = bhum.createHeadsUpEntry(notifEntry);
+ final HeadsUpManagerImpl.HeadsUpEntry headsUpEntry = bhum.createHeadsUpEntry(notifEntry);
mAvalancheController.addToNext(headsUpEntry, () -> {});
assertThat(bhum.getHeadsUpEntryList()).contains(headsUpEntry);
@@ -229,10 +229,10 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
@Test
@EnableFlags(NotificationThrottleHun.FLAG_NAME)
public void testGetHeadsUpEntry_returnsAvalancheEntry() {
- final BaseHeadsUpManager bhum = createHeadsUpManager();
+ final HeadsUpManagerImpl bhum = createHeadsUpManager();
final NotificationEntry notifEntry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0,
mContext);
- final BaseHeadsUpManager.HeadsUpEntry headsUpEntry = bhum.createHeadsUpEntry(notifEntry);
+ final HeadsUpManagerImpl.HeadsUpEntry headsUpEntry = bhum.createHeadsUpEntry(notifEntry);
mAvalancheController.addToNext(headsUpEntry, () -> {});
assertThat(bhum.getHeadsUpEntry(notifEntry.getKey())).isEqualTo(headsUpEntry);
@@ -240,7 +240,7 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
@Test
public void testShowNotification_addsEntry() {
- final BaseHeadsUpManager alm = createHeadsUpManager();
+ final HeadsUpManagerImpl alm = createHeadsUpManager();
final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
alm.showNotification(entry);
@@ -252,7 +252,7 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
@Test
public void testShowNotification_autoDismisses() {
- final BaseHeadsUpManager alm = createHeadsUpManager();
+ final HeadsUpManagerImpl alm = createHeadsUpManager();
final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
alm.showNotification(entry);
@@ -263,7 +263,7 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
@Test
public void testRemoveNotification_removeDeferred() {
- final BaseHeadsUpManager alm = createHeadsUpManager();
+ final HeadsUpManagerImpl alm = createHeadsUpManager();
final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
alm.showNotification(entry);
@@ -276,7 +276,7 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
@Test
public void testRemoveNotification_forceRemove() {
- final BaseHeadsUpManager alm = createHeadsUpManager();
+ final HeadsUpManagerImpl alm = createHeadsUpManager();
final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
alm.showNotification(entry);
@@ -289,7 +289,7 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
@Test
public void testReleaseAllImmediately() {
- final BaseHeadsUpManager alm = createHeadsUpManager();
+ final HeadsUpManagerImpl alm = createHeadsUpManager();
for (int i = 0; i < TEST_NUM_NOTIFICATIONS; i++) {
final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(i, mContext);
entry.setRow(mRow);
@@ -303,7 +303,7 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
@Test
public void testCanRemoveImmediately_notShownLongEnough() {
- final BaseHeadsUpManager alm = createHeadsUpManager();
+ final HeadsUpManagerImpl alm = createHeadsUpManager();
final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
alm.showNotification(entry);
@@ -314,11 +314,13 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
@Test
public void testHunRemovedLogging() {
- final BaseHeadsUpManager hum = createHeadsUpManager();
+ final HeadsUpManagerImpl hum = createHeadsUpManager();
final NotificationEntry notifEntry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0,
mContext);
- final BaseHeadsUpManager.HeadsUpEntry headsUpEntry = mock(
- BaseHeadsUpManager.HeadsUpEntry.class);
+ final HeadsUpManagerImpl.HeadsUpEntry headsUpEntry = mock(
+ HeadsUpManagerImpl.HeadsUpEntry.class);
+ when(headsUpEntry.getPinnedStatus())
+ .thenReturn(StateFlowKt.MutableStateFlow(PinnedStatus.NotPinned));
headsUpEntry.mEntry = notifEntry;
hum.onEntryRemoved(headsUpEntry, "test");
@@ -329,7 +331,7 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
@Test
public void testShowNotification_autoDismissesIncludingTouchAcceptanceDelay() {
- final BaseHeadsUpManager hum = createHeadsUpManager();
+ final HeadsUpManagerImpl hum = createHeadsUpManager();
final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
useAccessibilityTimeout(false);
@@ -342,7 +344,7 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
@Test
public void testShowNotification_autoDismissesWithDefaultTimeout() {
- final BaseHeadsUpManager hum = createHeadsUpManager();
+ final HeadsUpManagerImpl hum = createHeadsUpManager();
final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
useAccessibilityTimeout(false);
@@ -356,7 +358,7 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
@Test
public void testShowNotification_stickyForSomeTime_autoDismissesWithStickyTimeout() {
- final BaseHeadsUpManager hum = createHeadsUpManager();
+ final HeadsUpManagerImpl hum = createHeadsUpManager();
final NotificationEntry entry = createStickyForSomeTimeEntry(/* id = */ 0);
useAccessibilityTimeout(false);
@@ -370,7 +372,7 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
@Test
public void testShowNotification_sticky_neverAutoDismisses() {
- final BaseHeadsUpManager hum = createHeadsUpManager();
+ final HeadsUpManagerImpl hum = createHeadsUpManager();
final NotificationEntry entry = createStickyEntry(/* id = */ 0);
useAccessibilityTimeout(false);
@@ -383,7 +385,7 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
@Test
public void testShowNotification_autoDismissesWithAccessibilityTimeout() {
- final BaseHeadsUpManager hum = createHeadsUpManager();
+ final HeadsUpManagerImpl hum = createHeadsUpManager();
final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
useAccessibilityTimeout(true);
@@ -397,7 +399,7 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
@Test
public void testShowNotification_stickyForSomeTime_autoDismissesWithAccessibilityTimeout() {
- final BaseHeadsUpManager hum = createHeadsUpManager();
+ final HeadsUpManagerImpl hum = createHeadsUpManager();
final NotificationEntry entry = createStickyForSomeTimeEntry(/* id = */ 0);
useAccessibilityTimeout(true);
@@ -411,7 +413,7 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
@Test
public void testRemoveNotification_beforeMinimumDisplayTime() {
- final BaseHeadsUpManager hum = createHeadsUpManager();
+ final HeadsUpManagerImpl hum = createHeadsUpManager();
final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
useAccessibilityTimeout(false);
@@ -430,7 +432,7 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
@Test
public void testRemoveNotification_afterMinimumDisplayTime() {
- final BaseHeadsUpManager hum = createHeadsUpManager();
+ final HeadsUpManagerImpl hum = createHeadsUpManager();
final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
useAccessibilityTimeout(false);
@@ -448,7 +450,7 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
@Test
public void testRemoveNotification_releaseImmediately() {
- final BaseHeadsUpManager hum = createHeadsUpManager();
+ final HeadsUpManagerImpl hum = createHeadsUpManager();
final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext);
hum.showNotification(entry);
@@ -462,7 +464,7 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
@Test
public void testIsSticky_rowPinnedAndExpanded_true() {
- final BaseHeadsUpManager hum = createHeadsUpManager();
+ final HeadsUpManagerImpl hum = createHeadsUpManager();
final NotificationEntry notifEntry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0,
mContext);
when(mRow.isPinned()).thenReturn(true);
@@ -470,7 +472,7 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
hum.showNotification(notifEntry);
- final BaseHeadsUpManager.HeadsUpEntry headsUpEntry = hum.getHeadsUpEntry(
+ final HeadsUpManagerImpl.HeadsUpEntry headsUpEntry = hum.getHeadsUpEntry(
notifEntry.getKey());
headsUpEntry.setExpanded(true);
@@ -479,13 +481,13 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
@Test
public void testIsSticky_remoteInputActive_true() {
- final BaseHeadsUpManager hum = createHeadsUpManager();
+ final HeadsUpManagerImpl hum = createHeadsUpManager();
final NotificationEntry notifEntry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0,
mContext);
hum.showNotification(notifEntry);
- final BaseHeadsUpManager.HeadsUpEntry headsUpEntry = hum.getHeadsUpEntry(
+ final HeadsUpManagerImpl.HeadsUpEntry headsUpEntry = hum.getHeadsUpEntry(
notifEntry.getKey());
headsUpEntry.mRemoteInputActive = true;
@@ -494,7 +496,7 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
@Test
public void testIsSticky_hasFullScreenIntent_true() {
- final BaseHeadsUpManager hum = createHeadsUpManager();
+ final HeadsUpManagerImpl hum = createHeadsUpManager();
final NotificationEntry notifEntry =
HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id = */ 0, mContext);
@@ -506,7 +508,7 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
@Test
public void testIsSticky_stickyForSomeTime_false() {
- final BaseHeadsUpManager hum = createHeadsUpManager();
+ final HeadsUpManagerImpl hum = createHeadsUpManager();
final NotificationEntry entry = createStickyForSomeTimeEntry(/* id = */ 0);
hum.showNotification(entry);
@@ -517,13 +519,13 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
@Test
public void testIsSticky_false() {
- final BaseHeadsUpManager hum = createHeadsUpManager();
+ final HeadsUpManagerImpl hum = createHeadsUpManager();
final NotificationEntry notifEntry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0,
mContext);
hum.showNotification(notifEntry);
- final BaseHeadsUpManager.HeadsUpEntry headsUpEntry = hum.getHeadsUpEntry(
+ final HeadsUpManagerImpl.HeadsUpEntry headsUpEntry = hum.getHeadsUpEntry(
notifEntry.getKey());
headsUpEntry.setExpanded(false);
headsUpEntry.mRemoteInputActive = false;
@@ -533,7 +535,7 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
@Test
public void testCompareTo_withNullEntries() {
- final BaseHeadsUpManager hum = createHeadsUpManager();
+ final HeadsUpManagerImpl hum = createHeadsUpManager();
final NotificationEntry alertEntry = new NotificationEntryBuilder().setTag("alert").build();
hum.showNotification(alertEntry);
@@ -545,7 +547,7 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
@Test
public void testCompareTo_withNonAlertEntries() {
- final BaseHeadsUpManager hum = createHeadsUpManager();
+ final HeadsUpManagerImpl hum = createHeadsUpManager();
final NotificationEntry nonAlertEntry1 = new NotificationEntryBuilder().setTag(
"nae1").build();
@@ -561,9 +563,9 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
@Test
public void testAlertEntryCompareTo_ongoingCallLessThanActiveRemoteInput() {
- final BaseHeadsUpManager hum = createHeadsUpManager();
+ final HeadsUpManagerImpl hum = createHeadsUpManager();
- final BaseHeadsUpManager.HeadsUpEntry ongoingCall = hum.new HeadsUpEntry(
+ final HeadsUpManagerImpl.HeadsUpEntry ongoingCall = hum.new HeadsUpEntry(
new NotificationEntryBuilder()
.setSbn(HeadsUpManagerTestUtil.createSbn(/* id = */ 0,
new Notification.Builder(mContext, "")
@@ -571,7 +573,7 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
.setOngoing(true)))
.build());
- final BaseHeadsUpManager.HeadsUpEntry activeRemoteInput = hum.new HeadsUpEntry(
+ final HeadsUpManagerImpl.HeadsUpEntry activeRemoteInput = hum.new HeadsUpEntry(
HeadsUpManagerTestUtil.createEntry(/* id = */ 1, mContext));
activeRemoteInput.mRemoteInputActive = true;
@@ -581,11 +583,11 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
@Test
public void testAlertEntryCompareTo_incomingCallLessThanActiveRemoteInput() {
- final BaseHeadsUpManager hum = createHeadsUpManager();
+ final HeadsUpManagerImpl hum = createHeadsUpManager();
final Person person = new Person.Builder().setName("person").build();
final PendingIntent intent = mock(PendingIntent.class);
- final BaseHeadsUpManager.HeadsUpEntry incomingCall = hum.new HeadsUpEntry(
+ final HeadsUpManagerImpl.HeadsUpEntry incomingCall = hum.new HeadsUpEntry(
new NotificationEntryBuilder()
.setSbn(HeadsUpManagerTestUtil.createSbn(/* id = */ 0,
new Notification.Builder(mContext, "")
@@ -593,7 +595,7 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
.forIncomingCall(person, intent, intent))))
.build());
- final BaseHeadsUpManager.HeadsUpEntry activeRemoteInput = hum.new HeadsUpEntry(
+ final HeadsUpManagerImpl.HeadsUpEntry activeRemoteInput = hum.new HeadsUpEntry(
HeadsUpManagerTestUtil.createEntry(/* id = */ 1, mContext));
activeRemoteInput.mRemoteInputActive = true;
@@ -604,10 +606,10 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
@Test
@EnableFlags(NotificationThrottleHun.FLAG_NAME)
public void testPinEntry_logsPeek_throttleEnabled() {
- final BaseHeadsUpManager hum = createHeadsUpManager();
+ final HeadsUpManagerImpl hum = createHeadsUpManager();
// Needs full screen intent in order to be pinned
- final BaseHeadsUpManager.HeadsUpEntry entryToPin = hum.new HeadsUpEntry(
+ final HeadsUpManagerImpl.HeadsUpEntry entryToPin = hum.new HeadsUpEntry(
HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id = */ 0, mContext));
// Note: the standard way to show a notification would be calling showNotification rather
@@ -620,17 +622,17 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
assertEquals(2, mUiEventLoggerFake.numLogs());
assertEquals(AvalancheController.ThrottleEvent.AVALANCHE_THROTTLING_HUN_SHOWN.getId(),
mUiEventLoggerFake.eventId(0));
- assertEquals(BaseHeadsUpManager.NotificationPeekEvent.NOTIFICATION_PEEK.getId(),
+ assertEquals(HeadsUpManagerImpl.NotificationPeekEvent.NOTIFICATION_PEEK.getId(),
mUiEventLoggerFake.eventId(1));
}
@Test
@DisableFlags(NotificationThrottleHun.FLAG_NAME)
public void testPinEntry_logsPeek_throttleDisabled() {
- final BaseHeadsUpManager hum = createHeadsUpManager();
+ final HeadsUpManagerImpl hum = createHeadsUpManager();
// Needs full screen intent in order to be pinned
- final BaseHeadsUpManager.HeadsUpEntry entryToPin = hum.new HeadsUpEntry(
+ final HeadsUpManagerImpl.HeadsUpEntry entryToPin = hum.new HeadsUpEntry(
HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id = */ 0, mContext));
// Note: the standard way to show a notification would be calling showNotification rather
@@ -641,13 +643,13 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase {
hum.onEntryAdded(entryToPin);
assertEquals(1, mUiEventLoggerFake.numLogs());
- assertEquals(BaseHeadsUpManager.NotificationPeekEvent.NOTIFICATION_PEEK.getId(),
+ assertEquals(HeadsUpManagerImpl.NotificationPeekEvent.NOTIFICATION_PEEK.getId(),
mUiEventLoggerFake.eventId(0));
}
@Test
public void testSetUserActionMayIndirectlyRemove() {
- final BaseHeadsUpManager hum = createHeadsUpManager();
+ final HeadsUpManagerImpl hum = createHeadsUpManager();
final NotificationEntry notifEntry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0,
mContext);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerPhoneTest.kt
index 6175e05923a7..35d825310fdc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerPhoneTest.kt
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.systemui.statusbar.policy
+package com.android.systemui.statusbar.notification.headsup
import android.os.Handler
import android.platform.test.annotations.EnableFlags
@@ -35,6 +35,7 @@ import com.android.systemui.statusbar.notification.collection.render.GroupMember
import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
import com.android.systemui.statusbar.phone.ConfigurationControllerImpl
import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper
import com.android.systemui.testKosmos
import com.android.systemui.util.concurrency.mockExecutorHandler
import com.android.systemui.util.kotlin.JavaAdapter
@@ -58,7 +59,7 @@ import platform.test.runner.parameterized.Parameters
@SmallTest
@RunWith(ParameterizedAndroidJunit4::class)
@RunWithLooper
-class HeadsUpManagerPhoneTest(flags: FlagsParameterization) : BaseHeadsUpManagerTest(flags) {
+class HeadsUpManagerPhoneTest(flags: FlagsParameterization) : HeadsUpManagerImplTest(flags) {
private val mHeadsUpManagerLogger = HeadsUpManagerLogger(logcatLogBuffer())
@@ -87,8 +88,8 @@ class HeadsUpManagerPhoneTest(flags: FlagsParameterization) : BaseHeadsUpManager
@Mock private lateinit var mBgHandler: Handler
- private fun createHeadsUpManagerPhone(): BaseHeadsUpManager {
- return BaseHeadsUpManager(
+ private fun createHeadsUpManagerPhone(): HeadsUpManagerImpl {
+ return HeadsUpManagerImpl(
mContext,
mHeadsUpManagerLogger,
statusBarStateController,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTestUtil.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerTestUtil.java
index 306d6efd40b5..684ce59d6129 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTestUtil.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerTestUtil.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.policy;
+package com.android.systemui.statusbar.notification.headsup;
import android.app.ActivityManager;
import android.app.Notification;
import android.app.PendingIntent;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/TestableHeadsUpManager.java
index 59987f413ad6..2b077ed7f80d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/TestableHeadsUpManager.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.policy;
+package com.android.systemui.statusbar.notification.headsup;
import static com.android.systemui.util.concurrency.MockExecutorHandlerKt.mockExecutorHandler;
@@ -33,12 +33,14 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
+import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.settings.GlobalSettings;
import com.android.systemui.util.time.SystemClock;
-class TestableHeadsUpManager extends BaseHeadsUpManager {
+class TestableHeadsUpManager extends HeadsUpManagerImpl {
private HeadsUpEntry mLastCreatedEntry;
@@ -76,10 +78,10 @@ class TestableHeadsUpManager extends BaseHeadsUpManager {
shadeInteractor,
avalancheController);
- mTouchAcceptanceDelay = BaseHeadsUpManagerTest.TEST_TOUCH_ACCEPTANCE_TIME;
- mMinimumDisplayTime = BaseHeadsUpManagerTest.TEST_MINIMUM_DISPLAY_TIME;
- mAutoDismissTime = BaseHeadsUpManagerTest.TEST_AUTO_DISMISS_TIME;
- mStickyForSomeTimeAutoDismissTime = BaseHeadsUpManagerTest.TEST_STICKY_AUTO_DISMISS_TIME;
+ mTouchAcceptanceDelay = HeadsUpManagerImplTest.TEST_TOUCH_ACCEPTANCE_TIME;
+ mMinimumDisplayTime = HeadsUpManagerImplTest.TEST_MINIMUM_DISPLAY_TIME;
+ mAutoDismissTime = HeadsUpManagerImplTest.TEST_AUTO_DISMISS_TIME;
+ mStickyForSomeTimeAutoDismissTime = HeadsUpManagerImplTest.TEST_STICKY_AUTO_DISMISS_TIME;
}
@NonNull
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
index 284efc71a96d..d3bde84ad0c3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
@@ -70,13 +70,13 @@ import com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED
import com.android.systemui.statusbar.notification.NotifPipelineFlags
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.MAX_HUN_WHEN_AGE_MS
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.FSI_SUPPRESSED_SUPPRESSIVE_BUBBLE_METADATA
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.FSI_SUPPRESSED_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.HUN_SUPPRESSED_OLD_WHEN
import com.android.systemui.statusbar.policy.FakeDeviceProvisionedController
-import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.util.FakeEventLog
import com.android.systemui.util.settings.FakeGlobalSettings
import com.android.systemui.util.settings.FakeSettings
@@ -109,7 +109,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() {
override fun isTagLoggable(tagName: String, level: LogLevel): Boolean = true
},
- systrace = false
+ systrace = false,
)
private val leakCheck = LeakCheckedTest.SysuiLeakCheck()
@@ -162,10 +162,10 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() {
@Before
fun setUp() {
val userId = ActivityManager.getCurrentUser()
- val user = UserInfo(userId, "Current user", /* flags = */ 0)
+ val user = UserInfo(userId, "Current user", /* flags= */ 0)
deviceProvisionedController.currentUser = userId
- userTracker.set(listOf(user), /* currentUserIndex = */ 0)
+ userTracker.set(listOf(user), /* currentUserIndex= */ 0)
systemSettings = FakeSettings()
whenever(bubbles.canShowBubbleNotification()).thenReturn(true)
whenever(settingsInteractor.isCooldownEnabled).thenReturn(MutableStateFlow(true))
@@ -491,7 +491,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() {
private fun withPeekAndPulseEntry(
extendEntry: EntryBuilder.() -> Unit,
- block: (NotificationEntry) -> Unit
+ block: (NotificationEntry) -> Unit,
) {
ensurePeekState()
block(buildPeekEntry(extendEntry))
@@ -540,10 +540,10 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() {
@EnableFlags(android.service.notification.Flags.FLAG_NOTIFICATION_SILENT_FLAG)
fun testShouldNotHeadsUp_silentNotification() {
withPeekAndPulseEntry({
- isGrouped = false
- isGroupSummary = false
- isSilent = true
- }) {
+ isGrouped = false
+ isGroupSummary = false
+ isSilent = true
+ }) {
assertShouldNotHeadsUp(it)
assertNoEventsLogged()
}
@@ -553,10 +553,10 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() {
@EnableFlags(android.service.notification.Flags.FLAG_NOTIFICATION_SILENT_FLAG)
fun testShouldHeadsUp_silentNotificationFalse() {
withPeekAndPulseEntry({
- isGrouped = false
- isGroupSummary = false
- isSilent = false
- }) {
+ isGrouped = false
+ isGroupSummary = false
+ isSilent = false
+ }) {
assertShouldHeadsUp(it)
assertNoEventsLogged()
}
@@ -697,7 +697,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() {
forEachFsiState {
assertShouldNotFsi(
buildFsiEntry { suppressedVisualEffects = SUPPRESSED_EFFECT_FULL_SCREEN_INTENT },
- expectWouldInterruptWithoutDnd = true
+ expectWouldInterruptWithoutDnd = true,
)
assertNoEventsLogged()
}
@@ -719,7 +719,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() {
suppressedVisualEffects = SUPPRESSED_EFFECT_FULL_SCREEN_INTENT
importance = IMPORTANCE_DEFAULT
},
- expectWouldInterruptWithoutDnd = false
+ expectWouldInterruptWithoutDnd = false,
)
assertNoEventsLogged()
}
@@ -754,7 +754,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() {
assertUiEventLogged(
FSI_SUPPRESSED_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR,
entry.sbn.uid,
- entry.sbn.packageName
+ entry.sbn.packageName,
)
assertSystemEventLogged("231322873", entry.sbn.uid, "groupAlertBehavior")
}
@@ -813,7 +813,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() {
assertUiEventLogged(
FSI_SUPPRESSED_SUPPRESSIVE_BUBBLE_METADATA,
entry.sbn.uid,
- entry.sbn.packageName
+ entry.sbn.packageName,
)
assertSystemEventLogged("274759612", entry.sbn.uid, "bubbleMetadata")
}
@@ -960,7 +960,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() {
var keyguardIsShowing: Boolean = false,
var keyguardIsOccluded: Boolean = false,
var deviceProvisioned: Boolean = true,
- var currentUserSetup: Boolean = true
+ var currentUserSetup: Boolean = true,
)
protected fun setState(state: State): Unit =
@@ -1131,7 +1131,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() {
protected fun withLegacySuppressor(
suppressor: NotificationInterruptSuppressor,
- block: () -> Unit
+ block: () -> Unit,
) {
provider.addLegacySuppressor(suppressor)
block()
@@ -1166,7 +1166,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() {
protected fun assertShouldNotFsi(
entry: NotificationEntry,
- expectWouldInterruptWithoutDnd: Boolean? = null
+ expectWouldInterruptWithoutDnd: Boolean? = null,
) =
provider.makeUnloggedFullScreenIntentDecision(entry).let {
provider.logFullScreenIntentDecision(it)
@@ -1175,7 +1175,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() {
assertEquals(
"unexpected wouldInterruptWithoutDnd for FSI: ${it.logReason}",
expectWouldInterruptWithoutDnd,
- it.wouldInterruptWithoutDnd
+ it.wouldInterruptWithoutDnd,
)
}
}
@@ -1227,9 +1227,9 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() {
context,
/* requestCode = */ 0,
Intent().setPackage(context.packageName),
- FLAG_MUTABLE
+ FLAG_MUTABLE,
),
- Icon.createWithResource(context.resources, R.drawable.android)
+ Icon.createWithResource(context.resources, R.drawable.android),
)
}
@@ -1272,7 +1272,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() {
}
if (hasFsi) {
- nb.setFullScreenIntent(mock(), /* highPriority = */ true)
+ nb.setFullScreenIntent(mock(), /* highPriority= */ true)
}
}
.build()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestUtil.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestUtil.kt
index 7fd9c9fbdd6b..ff8ef189eb76 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestUtil.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestUtil.kt
@@ -27,9 +27,9 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.settings.UserTracker
import com.android.systemui.shared.notifications.domain.interactor.NotificationSettingsInteractor
import com.android.systemui.statusbar.notification.NotifPipelineFlags
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.DeviceProvisionedController
-import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.EventLog
import com.android.systemui.util.settings.GlobalSettings
@@ -63,7 +63,7 @@ object VisualInterruptionDecisionProviderTestUtil {
bubbles: Optional<Bubbles>,
context: Context,
notificationManager: NotificationManager,
- settingsInteractor: NotificationSettingsInteractor
+ settingsInteractor: NotificationSettingsInteractor,
): VisualInterruptionDecisionProvider {
return if (VisualInterruptionRefactor.isEnabled) {
VisualInterruptionDecisionProviderImpl(
@@ -88,7 +88,7 @@ object VisualInterruptionDecisionProviderTestUtil {
bubbles,
context,
notificationManager,
- settingsInteractor
+ settingsInteractor,
)
} else {
NotificationInterruptStateProviderWrapper(
@@ -109,7 +109,7 @@ object VisualInterruptionDecisionProviderTestUtil {
systemClock,
globalSettings,
eventLog,
- bubbles
+ bubbles,
)
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorTest.kt
new file mode 100644
index 000000000000..6736ccf739ce
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorTest.kt
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.promoted
+
+import android.app.Notification
+import android.app.Notification.BigPictureStyle
+import android.app.Notification.BigTextStyle
+import android.app.Notification.CallStyle
+import android.app.Notification.MessagingStyle
+import android.app.Notification.ProgressStyle
+import android.app.Notification.ProgressStyle.Segment
+import android.app.PendingIntent
+import android.app.Person
+import android.content.Intent
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel.Style
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class PromotedNotificationContentExtractorTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+
+ private val provider =
+ FakePromotedNotificationsProvider().also { kosmos.promotedNotificationsProvider = it }
+
+ private val underTest = kosmos.promotedNotificationContentExtractor
+
+ @Test
+ @DisableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
+ fun shouldNotExtract_bothFlagsDisabled() {
+ val notif = createEntry().also { provider.promotedEntries.add(it) }
+ val content = extractContent(notif)
+ assertThat(content).isNull()
+ }
+
+ @Test
+ @EnableFlags(PromotedNotificationUi.FLAG_NAME)
+ @DisableFlags(StatusBarNotifChips.FLAG_NAME)
+ fun shouldExtract_promotedNotificationUiFlagEnabled() {
+ val entry = createEntry().also { provider.promotedEntries.add(it) }
+ val content = extractContent(entry)
+ assertThat(content).isNotNull()
+ }
+
+ @Test
+ @EnableFlags(StatusBarNotifChips.FLAG_NAME)
+ @DisableFlags(PromotedNotificationUi.FLAG_NAME)
+ fun shouldExtract_statusBarNotifChipsFlagEnabled() {
+ val entry = createEntry().also { provider.promotedEntries.add(it) }
+ val content = extractContent(entry)
+ assertThat(content).isNotNull()
+ }
+
+ @Test
+ @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
+ fun shouldExtract_bothFlagsEnabled() {
+ val entry = createEntry().also { provider.promotedEntries.add(it) }
+ val content = extractContent(entry)
+ assertThat(content).isNotNull()
+ }
+
+ @Test
+ @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
+ fun shouldNotExtract_providerDidNotPromote() {
+ val entry = createEntry().also { provider.promotedEntries.remove(it) }
+ val content = extractContent(entry)
+ assertThat(content).isNull()
+ }
+
+ @Test
+ @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
+ fun extractContent_commonFields() {
+ val entry =
+ createEntry {
+ setSubText(TEST_SUB_TEXT)
+ setContentTitle(TEST_CONTENT_TITLE)
+ setContentText(TEST_CONTENT_TEXT)
+ }
+ .also { provider.promotedEntries.add(it) }
+
+ val content = extractContent(entry)
+
+ assertThat(content).isNotNull()
+ assertThat(content?.subText).isEqualTo(TEST_SUB_TEXT)
+ assertThat(content?.title).isEqualTo(TEST_CONTENT_TITLE)
+ assertThat(content?.text).isEqualTo(TEST_CONTENT_TEXT)
+ }
+
+ @Test
+ @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
+ fun extractContent_fromBigPictureStyle() {
+ val entry =
+ createEntry { setStyle(BigPictureStyle()) }.also { provider.promotedEntries.add(it) }
+
+ val content = extractContent(entry)
+
+ assertThat(content).isNotNull()
+ assertThat(content?.style).isEqualTo(Style.BigPicture)
+ }
+
+ @Test
+ @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
+ fun extractContent_fromBigTextStyle() {
+ val entry =
+ createEntry { setStyle(BigTextStyle()) }.also { provider.promotedEntries.add(it) }
+
+ val content = extractContent(entry)
+
+ assertThat(content).isNotNull()
+ assertThat(content?.style).isEqualTo(Style.BigText)
+ }
+
+ @Test
+ @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
+ fun extractContent_fromCallStyle() {
+ val hangUpIntent =
+ PendingIntent.getBroadcast(context, 0, Intent("hangup"), PendingIntent.FLAG_IMMUTABLE)
+
+ val entry =
+ createEntry { setStyle(CallStyle.forOngoingCall(TEST_PERSON, hangUpIntent)) }
+ .also { provider.promotedEntries.add(it) }
+
+ val content = extractContent(entry)
+
+ assertThat(content).isNotNull()
+ assertThat(content?.style).isEqualTo(Style.Call)
+ }
+
+ @Test
+ @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
+ fun extractContent_fromProgressStyle() {
+ val entry =
+ createEntry {
+ setStyle(ProgressStyle().addProgressSegment(Segment(100)).setProgress(75))
+ }
+ .also { provider.promotedEntries.add(it) }
+
+ val content = extractContent(entry)
+
+ assertThat(content).isNotNull()
+ assertThat(content?.style).isEqualTo(Style.Progress)
+ assertThat(content?.progress).isNotNull()
+ assertThat(content?.progress?.progress).isEqualTo(75)
+ assertThat(content?.progress?.progressMax).isEqualTo(100)
+ }
+
+ @Test
+ @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
+ fun extractContent_fromIneligibleStyle() {
+ val entry =
+ createEntry {
+ setStyle(
+ MessagingStyle(TEST_PERSON).addMessage("message text", 0L, TEST_PERSON)
+ )
+ }
+ .also { provider.promotedEntries.add(it) }
+
+ val content = extractContent(entry)
+
+ assertThat(content).isNotNull()
+ assertThat(content?.style).isEqualTo(Style.Ineligible)
+ }
+
+ private fun extractContent(entry: NotificationEntry): PromotedNotificationContentModel? {
+ val recoveredBuilder = Notification.Builder(context, entry.sbn.notification)
+ return underTest.extractContent(entry, recoveredBuilder)
+ }
+
+ private fun createEntry(builderBlock: Notification.Builder.() -> Unit = {}): NotificationEntry {
+ val notif = Notification.Builder(context, "a").also(builderBlock).build()
+ return NotificationEntryBuilder().setNotification(notif).build()
+ }
+
+ companion object {
+ private const val TEST_SUB_TEXT = "sub text"
+ private const val TEST_CONTENT_TITLE = "content title"
+ private const val TEST_CONTENT_TEXT = "content text"
+
+ private const val TEST_PERSON_NAME = "person name"
+ private const val TEST_PERSON_KEY = "person key"
+ private val TEST_PERSON =
+ Person.Builder().setKey(TEST_PERSON_KEY).setName(TEST_PERSON_NAME).build()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
index 657e9df0029b..ca0f9ef5f2b0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
@@ -42,6 +42,7 @@ import com.android.systemui.statusbar.notification.collection.provider.Notificat
import com.android.systemui.statusbar.notification.collection.render.FakeNodeController
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController.BUBBLES_SETTING_URI
import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer
@@ -49,7 +50,6 @@ import com.android.systemui.statusbar.notification.stack.NotificationChildrenCon
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
import com.android.systemui.statusbar.notification.stack.ui.view.NotificationRowStatsLogger
import com.android.systemui.statusbar.phone.KeyguardBypassController
-import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.statusbar.policy.SmartReplyConstants
import com.android.systemui.statusbar.policy.dagger.RemoteInputViewSubcomponent
import com.android.systemui.util.mockito.any
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
index 1c5f37cc60c3..979a1d0801f0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
@@ -40,8 +40,9 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.shade.ShadeController;
+import com.android.systemui.statusbar.notification.headsup.PinnedStatus;
import com.android.systemui.statusbar.notification.logging.NotificationPanelLogger;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
import org.junit.Before;
import org.junit.Test;
@@ -91,7 +92,7 @@ public class ExpandableNotificationRowDragControllerTest extends SysuiTestCase {
ExpandableNotificationRowDragController controller = createSpyController();
mRow.setDragController(controller);
mRow.setHeadsUp(true);
- mRow.setPinned(true);
+ mRow.setPinnedStatus(PinnedStatus.PinnedBySystem);
mRow.doLongClickCallback(0, 0);
mRow.doDragCallback(0, 0);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
index b278f1a48b3d..6eb2764165b5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
@@ -30,6 +30,7 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -42,6 +43,7 @@ import android.os.CancellationSignal;
import android.os.Handler;
import android.os.Looper;
import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
import android.util.TypedValue;
@@ -56,8 +58,12 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.media.controls.util.MediaFeatureFlag;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips;
import com.android.systemui.statusbar.notification.ConversationNotificationProcessor;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractor;
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi;
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationCallback;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
@@ -99,6 +105,7 @@ public class NotificationContentInflaterTest extends SysuiTestCase {
@Mock private NotifLayoutInflaterFactory.Provider mNotifLayoutInflaterFactoryProvider;
@Mock private HeadsUpStyleProvider mHeadsUpStyleProvider;
@Mock private NotifLayoutInflaterFactory mNotifLayoutInflaterFactory;
+ @Mock private PromotedNotificationContentExtractor mPromotedNotificationContentExtractor;
private final SmartReplyStateInflater mSmartReplyStateInflater =
new SmartReplyStateInflater() {
@@ -142,6 +149,7 @@ public class NotificationContentInflaterTest extends SysuiTestCase {
mSmartReplyStateInflater,
mNotifLayoutInflaterFactoryProvider,
mHeadsUpStyleProvider,
+ mPromotedNotificationContentExtractor,
mock(NotificationRowContentBinderLogger.class));
}
@@ -382,6 +390,75 @@ public class NotificationContentInflaterTest extends SysuiTestCase {
verify(mRow, times(0)).onNotificationUpdated();
}
+ @Test
+ @DisableFlags({PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME})
+ public void testExtractsPromotedContent_notWhenBothFlagsDisabled() throws Exception {
+ final PromotedNotificationContentModel content =
+ new PromotedNotificationContentModel.Builder("key").build();
+ when(mPromotedNotificationContentExtractor.extractContent(any(), any()))
+ .thenReturn(content);
+
+ inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_ALL, mRow);
+
+ verify(mPromotedNotificationContentExtractor, never()).extractContent(any(), any());
+ }
+
+ @Test
+ @EnableFlags(PromotedNotificationUi.FLAG_NAME)
+ @DisableFlags(StatusBarNotifChips.FLAG_NAME)
+ public void testExtractsPromotedContent_whenPromotedNotificationUiFlagEnabled()
+ throws Exception {
+ final PromotedNotificationContentModel content =
+ new PromotedNotificationContentModel.Builder("key").build();
+ when(mPromotedNotificationContentExtractor.extractContent(any(), any()))
+ .thenReturn(content);
+
+ inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_ALL, mRow);
+
+ verify(mPromotedNotificationContentExtractor, times(1)).extractContent(any(), any());
+ assertEquals(content, mRow.getEntry().getPromotedNotificationContentModel());
+ }
+
+ @Test
+ @EnableFlags(StatusBarNotifChips.FLAG_NAME)
+ @DisableFlags(PromotedNotificationUi.FLAG_NAME)
+ public void testExtractsPromotedContent_whenStatusBarNotifChipsFlagEnabled() throws Exception {
+ final PromotedNotificationContentModel content =
+ new PromotedNotificationContentModel.Builder("key").build();
+ when(mPromotedNotificationContentExtractor.extractContent(any(), any()))
+ .thenReturn(content);
+
+ inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_ALL, mRow);
+
+ verify(mPromotedNotificationContentExtractor, times(1)).extractContent(any(), any());
+ assertEquals(content, mRow.getEntry().getPromotedNotificationContentModel());
+ }
+
+ @Test
+ @EnableFlags({PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME})
+ public void testExtractsPromotedContent_whenBothFlagsEnabled() throws Exception {
+ final PromotedNotificationContentModel content =
+ new PromotedNotificationContentModel.Builder("key").build();
+ when(mPromotedNotificationContentExtractor.extractContent(any(), any()))
+ .thenReturn(content);
+
+ inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_ALL, mRow);
+
+ verify(mPromotedNotificationContentExtractor, times(1)).extractContent(any(), any());
+ assertEquals(content, mRow.getEntry().getPromotedNotificationContentModel());
+ }
+
+ @Test
+ @EnableFlags({PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME})
+ public void testExtractsPromotedContent_null() throws Exception {
+ when(mPromotedNotificationContentExtractor.extractContent(any(), any())).thenReturn(null);
+
+ inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_ALL, mRow);
+
+ verify(mPromotedNotificationContentExtractor, times(1)).extractContent(any(), any());
+ assertNull(mRow.getEntry().getPromotedNotificationContentModel());
+ }
+
private static void inflateAndWait(NotificationContentInflater inflater,
@InflationFlag int contentToInflate,
ExpandableNotificationRow row)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt
index b16d3ea5337b..6a0a5bb3b191 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt
@@ -71,10 +71,10 @@ import com.android.systemui.statusbar.notification.AssistantFeedbackController
import com.android.systemui.statusbar.notification.NotificationActivityStarter
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider
import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
import com.android.systemui.statusbar.policy.DeviceProvisionedController
-import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.testKosmos
import com.android.systemui.util.kotlin.JavaAdapter
import com.android.systemui.wmshell.BubblesManager
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
index 48608ebd6de0..18517998096a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
@@ -21,6 +21,7 @@ import android.content.Context
import android.os.AsyncTask
import android.os.Build
import android.os.CancellationSignal
+import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.testing.TestableLooper.RunWithLooper
import android.util.TypedValue
@@ -33,8 +34,12 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.res.R
+import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import com.android.systemui.statusbar.notification.ConversationNotificationProcessor
import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractor
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED
@@ -62,6 +67,7 @@ import org.junit.runner.RunWith
import org.mockito.kotlin.any
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
import org.mockito.kotlin.spy
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
@@ -82,7 +88,7 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
object : NotifLayoutInflaterFactory.Provider {
override fun provide(
row: ExpandableNotificationRow,
- layoutType: Int
+ layoutType: Int,
): NotifLayoutInflaterFactory = mock()
}
private val smartReplyStateInflater: SmartReplyStateInflater =
@@ -95,7 +101,7 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
notifPackageContext: Context,
entry: NotificationEntry,
existingSmartReplyState: InflatedSmartReplyState?,
- newSmartReplyState: InflatedSmartReplyState
+ newSmartReplyState: InflatedSmartReplyState,
): InflatedSmartReplyViewHolder {
return inflatedSmartReplies
}
@@ -104,6 +110,7 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
return inflatedSmartReplyState
}
}
+ private val promotedNotificationContentExtractor: PromotedNotificationContentExtractor = mock()
@Before
fun setUp() {
@@ -125,7 +132,8 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
smartReplyStateInflater,
layoutInflaterFactoryProvider,
mock<HeadsUpStyleProvider>(),
- mock()
+ promotedNotificationContentExtractor,
+ mock(),
)
}
@@ -142,7 +150,8 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
FLAG_CONTENT_VIEW_ALL,
builder,
mContext,
- smartReplyStateInflater
+ smartReplyStateInflater,
+ mock(),
)
verify(builder).createHeadsUpContentView(true)
}
@@ -160,7 +169,8 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
FLAG_CONTENT_VIEW_ALL,
builder,
mContext,
- smartReplyStateInflater
+ smartReplyStateInflater,
+ mock(),
)
verify(builder).createContentView(true)
}
@@ -187,7 +197,7 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
true /* expectingException */,
notificationInflater,
FLAG_CONTENT_VIEW_ALL,
- row
+ row,
)
Assert.assertTrue(row.privateLayout.childCount == 0)
verify(row, times(0)).onNotificationUpdated()
@@ -210,7 +220,7 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
FLAG_CONTENT_VIEW_ALL,
BindParams(),
false /* forceInflate */,
- null /* callback */
+ null, /* callback */
)
Assert.assertNull(row.entry.runningTask)
}
@@ -223,7 +233,8 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
NotificationRowContentBinderImpl.InflationProgress(
packageContext = mContext,
remoteViews = NewRemoteViews(),
- contentModel = NotificationContentModel(headsUpStatusBarModel)
+ contentModel = NotificationContentModel(headsUpStatusBarModel),
+ extractedPromotedNotificationContentModel = null,
)
val countDownLatch = CountDownLatch(1)
NotificationRowContentBinderImpl.applyRemoteView(
@@ -261,7 +272,7 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
get() =
AsyncFailRemoteView(
mContext.packageName,
- com.android.systemui.tests.R.layout.custom_view_dark
+ com.android.systemui.tests.R.layout.custom_view_dark,
)
},
logger = mock(),
@@ -280,7 +291,7 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
val decoratedMediaView = builder.createContentView()
Assert.assertFalse(
"The decorated media style doesn't allow a view to be reapplied!",
- NotificationRowContentBinderImpl.canReapplyRemoteView(mediaView, decoratedMediaView)
+ NotificationRowContentBinderImpl.canReapplyRemoteView(mediaView, decoratedMediaView),
)
}
@@ -304,7 +315,7 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
Assert.assertEquals(
"Binder inflated a new view even though the old one was cached and usable.",
view,
- row.privateLayout.contractedChild
+ row.privateLayout.contractedChild,
)
}
@@ -327,7 +338,7 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
Assert.assertNotEquals(
"Binder (somehow) used the same view when inflating.",
view,
- row.privateLayout.contractedChild
+ row.privateLayout.contractedChild,
)
}
@@ -396,7 +407,7 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
private fun getValidationError(
measuredHeightDp: Float,
targetSdk: Int,
- contentView: RemoteViews?
+ contentView: RemoteViews?,
): String? {
val view: View = mock()
whenever(view.measuredHeight)
@@ -404,7 +415,7 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
TypedValue.applyDimension(
COMPLEX_UNIT_SP,
measuredHeightDp,
- mContext.resources.displayMetrics
+ mContext.resources.displayMetrics,
)
.toInt()
)
@@ -419,7 +430,7 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
row.entry.sbn.notification.contentView =
RemoteViews(
mContext.packageName,
- com.android.systemui.tests.R.layout.invalid_notification_height
+ com.android.systemui.tests.R.layout.invalid_notification_height,
)
inflateAndWait(true, notificationInflater, FLAG_CONTENT_VIEW_ALL, row)
Assert.assertEquals(0, row.privateLayout.childCount.toLong())
@@ -451,7 +462,7 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
false,
notificationInflater,
FLAG_CONTENT_VIEW_PUBLIC_SINGLE_LINE,
- messagingRow
+ messagingRow,
)
Assert.assertNotNull(messagingRow.publicLayout.mSingleLineView)
// assert this is the conversation layout
@@ -460,6 +471,59 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
)
}
+ @Test
+ @DisableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
+ fun testExtractsPromotedContent_notWhenBothFlagsDisabled() {
+ val content = PromotedNotificationContentModel.Builder("key").build()
+ whenever(promotedNotificationContentExtractor.extractContent(any(), any()))
+ .thenReturn(content)
+
+ inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_ALL, row)
+
+ verify(promotedNotificationContentExtractor, never()).extractContent(any(), any())
+ }
+
+ @Test
+ @EnableFlags(PromotedNotificationUi.FLAG_NAME)
+ @DisableFlags(StatusBarNotifChips.FLAG_NAME)
+ fun testExtractsPromotedContent_whenPromotedNotificationUiFlagEnabled() {
+ val content = PromotedNotificationContentModel.Builder("key").build()
+ whenever(promotedNotificationContentExtractor.extractContent(any(), any()))
+ .thenReturn(content)
+
+ inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_ALL, row)
+
+ verify(promotedNotificationContentExtractor, times(1)).extractContent(any(), any())
+ Assert.assertEquals(content, row.entry.promotedNotificationContentModel)
+ }
+
+ @Test
+ @EnableFlags(StatusBarNotifChips.FLAG_NAME)
+ @DisableFlags(PromotedNotificationUi.FLAG_NAME)
+ fun testExtractsPromotedContent_whenStatusBarNotifChipsFlagEnabled() {
+ val content = PromotedNotificationContentModel.Builder("key").build()
+ whenever(promotedNotificationContentExtractor.extractContent(any(), any()))
+ .thenReturn(content)
+
+ inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_ALL, row)
+
+ verify(promotedNotificationContentExtractor, times(1)).extractContent(any(), any())
+ Assert.assertEquals(content, row.entry.promotedNotificationContentModel)
+ }
+
+ @Test
+ @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
+ fun testExtractsPromotedContent_whenBothFlagsEnabled() {
+ val content = PromotedNotificationContentModel.Builder("key").build()
+ whenever(promotedNotificationContentExtractor.extractContent(any(), any()))
+ .thenReturn(content)
+
+ inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_ALL, row)
+
+ verify(promotedNotificationContentExtractor, times(1)).extractContent(any(), any())
+ Assert.assertEquals(content, row.entry.promotedNotificationContentModel)
+ }
+
private class ExceptionHolder {
var exception: Exception? = null
}
@@ -476,7 +540,7 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
parent: ViewGroup,
executor: Executor,
listener: OnViewAppliedListener,
- handler: InteractionHandler?
+ handler: InteractionHandler?,
): CancellationSignal {
executor.execute { listener.onError(RuntimeException("Failed to inflate async")) }
return CancellationSignal()
@@ -486,7 +550,7 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
context: Context,
parent: ViewGroup,
executor: Executor,
- listener: OnViewAppliedListener
+ listener: OnViewAppliedListener,
): CancellationSignal {
return applyAsync(context, parent, executor, listener, null)
}
@@ -496,7 +560,7 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
private fun inflateAndWait(
inflater: NotificationRowContentBinderImpl,
@InflationFlag contentToInflate: Int,
- row: ExpandableNotificationRow
+ row: ExpandableNotificationRow,
) {
inflateAndWait(false /* expectingException */, inflater, contentToInflate, row)
}
@@ -535,7 +599,7 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
contentToInflate,
BindParams(),
false /* forceInflate */,
- callback /* callback */
+ callback, /* callback */
)
Assert.assertTrue(countDownLatch.await(500, TimeUnit.MILLISECONDS))
exceptionHolder.exception?.let { throw it }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index 2340d0289db4..080ac3f8c697 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -82,13 +82,14 @@ import com.android.systemui.statusbar.notification.collection.render.GroupMember
import com.android.systemui.statusbar.notification.icon.IconBuilder;
import com.android.systemui.statusbar.notification.icon.IconManager;
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractor;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.ExpandableNotificationRowLogger;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.OnExpandClickListener;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
import com.android.systemui.statusbar.notification.row.shared.NotificationRowContentBinderRefactor;
import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainerLogger;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
import com.android.systemui.statusbar.policy.InflatedSmartReplyState;
import com.android.systemui.statusbar.policy.InflatedSmartReplyViewHolder;
import com.android.systemui.statusbar.policy.SmartReplyConstants;
@@ -204,6 +205,7 @@ public class NotificationTestHelper {
new MockSmartReplyInflater(),
mock(NotifLayoutInflaterFactory.Provider.class),
mock(HeadsUpStyleProvider.class),
+ mock(PromotedNotificationContentExtractor.class),
mock(NotificationRowContentBinderLogger.class))
: new NotificationContentInflater(
mock(NotifRemoteViewCache.class),
@@ -214,6 +216,7 @@ public class NotificationTestHelper {
new MockSmartReplyInflater(),
mock(NotifLayoutInflaterFactory.Provider.class),
mock(HeadsUpStyleProvider.class),
+ mock(PromotedNotificationContentExtractor.class),
mock(NotificationRowContentBinderLogger.class));
contentBinder.setInflateSynchronously(true);
mBindStage = new RowContentBindStage(contentBinder,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt
index 07935e47845e..92b8c3aa84d6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt
@@ -24,8 +24,8 @@ import com.android.systemui.flags.DisableSceneContainer
import com.android.systemui.flags.andSceneContainer
import com.android.systemui.shade.transition.LargeScreenShadeInterpolator
import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.notification.headsup.AvalancheController
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
-import com.android.systemui.statusbar.policy.AvalancheController
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 6425da46fc67..de40abb4d9d8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -83,7 +83,7 @@ import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.ColorUpdateLogger;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
-import com.android.systemui.statusbar.notification.HeadsUpTouchHelper;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpTouchHelper;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.collection.NotifCollection;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -107,7 +107,7 @@ import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController;
import com.android.systemui.statusbar.policy.ZenModeController;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
index b877456ab604..2b7e95062716 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -27,11 +27,11 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.emptyshade.ui.view.EmptyShadeView
import com.android.systemui.statusbar.notification.footer.ui.view.FooterView
import com.android.systemui.statusbar.notification.footer.ui.view.FooterView.FooterViewState
+import com.android.systemui.statusbar.notification.headsup.AvalancheController
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
-import com.android.systemui.statusbar.policy.AvalancheController
import com.google.common.truth.Expect
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
index bf144729dea3..e592e4b319e3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
@@ -42,6 +42,7 @@ import com.android.systemui.statusbar.notification.data.repository.activeNotific
import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs
import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
+import com.android.systemui.statusbar.notification.headsup.PinnedStatus
import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository
import com.android.systemui.statusbar.policy.data.repository.fakeUserSetupRepository
import com.android.systemui.statusbar.policy.fakeConfigurationController
@@ -522,21 +523,21 @@ class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCas
assertThat(pinnedHeadsUpRows).isEmpty()
// WHEN a row gets pinned
- rows[0].isPinned.value = true
+ rows[0].pinnedStatus.value = PinnedStatus.PinnedBySystem
runCurrent()
// THEN it's added to the list
assertThat(pinnedHeadsUpRows).containsExactly(rows[0])
// WHEN more rows are pinned
- rows[1].isPinned.value = true
+ rows[1].pinnedStatus.value = PinnedStatus.PinnedBySystem
runCurrent()
// THEN they are all in the list
assertThat(pinnedHeadsUpRows).containsExactly(rows[0], rows[1])
// WHEN a row gets unpinned
- rows[0].isPinned.value = false
+ rows[0].pinnedStatus.value = PinnedStatus.NotPinned
runCurrent()
// THEN it's removed from the list
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
index d9e94953e757..f76f1ce48a5c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
@@ -57,7 +57,7 @@ import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
index 64414050e0f4..ad8b67513b9f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
@@ -53,7 +53,7 @@ import com.android.systemui.statusbar.StatusBarStateControllerImpl;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
import org.junit.Before;
import org.junit.Test;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
index dd03ab393ce9..f9f2cd328094 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
@@ -45,13 +45,14 @@ import com.android.systemui.statusbar.HeadsUpStatusBarView;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationIconInteractor;
+import com.android.systemui.statusbar.notification.headsup.PinnedStatus;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation;
import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.policy.Clock;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import org.junit.Assert;
@@ -131,13 +132,13 @@ public class HeadsUpAppearanceControllerTest extends SysuiTestCase {
@Test
public void testShowinEntryUpdated() {
- mRow.setPinned(true);
+ mRow.setPinnedStatus(PinnedStatus.PinnedBySystem);
when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(true);
when(mHeadsUpManager.getTopEntry()).thenReturn(mEntry);
mHeadsUpAppearanceController.onHeadsUpPinned(mEntry);
assertEquals(mRow.getEntry(), mHeadsUpStatusBarView.getShowingEntry());
- mRow.setPinned(false);
+ mRow.setPinnedStatus(PinnedStatus.NotPinned);
when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(false);
mHeadsUpAppearanceController.onHeadsUpUnPinned(mEntry);
assertEquals(null, mHeadsUpStatusBarView.getShowingEntry());
@@ -145,13 +146,13 @@ public class HeadsUpAppearanceControllerTest extends SysuiTestCase {
@Test
public void testShownUpdated() {
- mRow.setPinned(true);
+ mRow.setPinnedStatus(PinnedStatus.PinnedBySystem);
when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(true);
when(mHeadsUpManager.getTopEntry()).thenReturn(mEntry);
mHeadsUpAppearanceController.onHeadsUpPinned(mEntry);
assertTrue(mHeadsUpAppearanceController.isShown());
- mRow.setPinned(false);
+ mRow.setPinnedStatus(PinnedStatus.NotPinned);
when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(false);
mHeadsUpAppearanceController.onHeadsUpUnPinned(mEntry);
Assert.assertFalse(mHeadsUpAppearanceController.isShown());
@@ -160,13 +161,13 @@ public class HeadsUpAppearanceControllerTest extends SysuiTestCase {
@Test
@DisableFlags(AsyncGroupHeaderViewInflation.FLAG_NAME)
public void testHeaderUpdated() {
- mRow.setPinned(true);
+ mRow.setPinnedStatus(PinnedStatus.PinnedBySystem);
when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(true);
when(mHeadsUpManager.getTopEntry()).thenReturn(mEntry);
mHeadsUpAppearanceController.onHeadsUpPinned(mEntry);
assertEquals(mRow.getHeaderVisibleAmount(), 0.0f, 0.0f);
- mRow.setPinned(false);
+ mRow.setPinnedStatus(PinnedStatus.NotPinned);
when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(false);
mHeadsUpAppearanceController.onHeadsUpUnPinned(mEntry);
assertEquals(mRow.getHeaderVisibleAmount(), 1.0f, 0.0f);
@@ -176,13 +177,13 @@ public class HeadsUpAppearanceControllerTest extends SysuiTestCase {
public void testOperatorNameViewUpdated() {
mHeadsUpAppearanceController.setAnimationsEnabled(false);
- mRow.setPinned(true);
+ mRow.setPinnedStatus(PinnedStatus.PinnedBySystem);
when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(true);
when(mHeadsUpManager.getTopEntry()).thenReturn(mEntry);
mHeadsUpAppearanceController.onHeadsUpPinned(mEntry);
assertEquals(View.INVISIBLE, mOperatorNameView.getVisibility());
- mRow.setPinned(false);
+ mRow.setPinnedStatus(PinnedStatus.NotPinned);
when(mHeadsUpManager.hasPinnedHeadsUp()).thenReturn(false);
mHeadsUpAppearanceController.onHeadsUpUnPinned(mEntry);
assertEquals(View.VISIBLE, mOperatorNameView.getVisibility());
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/MultiDisplayAutoHideControllerStoreTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/MultiDisplayAutoHideControllerStoreTest.kt
index 0b0b1e45d604..90506a1b9a7f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/MultiDisplayAutoHideControllerStoreTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/MultiDisplayAutoHideControllerStoreTest.kt
@@ -48,6 +48,11 @@ class MultiDisplayAutoHideControllerStoreTest : SysuiTestCase() {
@Before fun addDisplays() = runBlocking { fakeDisplayRepository.addDisplay(DEFAULT_DISPLAY) }
+ @Before
+ fun start() {
+ underTest.start()
+ }
+
@Test
fun beforeDisplayRemoved_doesNotStopInstances() =
testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ShadeTouchableRegionManagerTest.kt
index a00858842742..d82cb86406ec 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ShadeTouchableRegionManagerTest.kt
@@ -41,12 +41,12 @@ import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
@OptIn(ExperimentalCoroutinesApi::class)
-class StatusBarTouchableRegionManagerTest : SysuiTestCase() {
+class ShadeTouchableRegionManagerTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val sceneRepository = kosmos.sceneContainerRepository
- private val underTest by Lazy { kosmos.statusBarTouchableRegionManager }
+ private val underTest by Lazy { kosmos.shadeTouchableRegionManager }
@Test
@EnableSceneContainer
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
index 95472cad4b90..41782a123f14 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
@@ -73,7 +73,7 @@ import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/data/repository/KeyguardBypassRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/data/repository/KeyguardBypassRepositoryTest.kt
index a2fabf3b9baa..a2fabf3b9baa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/data/repository/KeyguardBypassRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/data/repository/KeyguardBypassRepositoryTest.kt
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogEventLoggerTest.kt b/packages/SystemUI/multivalentTests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogEventLoggerTest.kt
index 5146f77bbcf6..5146f77bbcf6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogEventLoggerTest.kt
+++ b/packages/SystemUI/multivalentTests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogEventLoggerTest.kt
diff --git a/packages/SystemUI/plugin/Android.bp b/packages/SystemUI/plugin/Android.bp
index 6212e2be4911..2cd334693486 100644
--- a/packages/SystemUI/plugin/Android.bp
+++ b/packages/SystemUI/plugin/Android.bp
@@ -55,6 +55,8 @@ java_library {
"SystemUICommon",
"SystemUILogLib",
"androidx.annotation_annotation",
+ "androidx.compose.ui_ui",
+ "androidx.compose.runtime_runtime",
],
}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockAnimations.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockAnimations.kt
new file mode 100644
index 000000000000..2df14a86e77c
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockAnimations.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+package com.android.systemui.plugins.clocks
+
+import android.view.View
+import com.android.systemui.plugins.annotations.ProtectedInterface
+
+/** Methods which trigger various clock animations */
+@ProtectedInterface
+interface ClockAnimations {
+ /** Runs an enter animation (if any) */
+ fun enter()
+
+ /** Sets how far into AOD the device currently is. */
+ fun doze(fraction: Float)
+
+ /** Sets how far into the folding animation the device is. */
+ fun fold(fraction: Float)
+
+ /** Runs the battery animation (if any). */
+ fun charge()
+
+ /**
+ * Runs when the clock's position changed during the move animation.
+ *
+ * @param fromLeft the [View.getLeft] position of the clock, before it started moving.
+ * @param direction the direction in which it is moving. A positive number means right, and
+ * negative means left.
+ * @param fraction fraction of the clock movement. 0 means it is at the beginning, and 1 means
+ * it finished moving.
+ * @deprecated use {@link #onPositionUpdated(float, float)} instead.
+ */
+ fun onPositionUpdated(fromLeft: Int, direction: Int, fraction: Float)
+
+ /**
+ * Runs when the clock's position changed during the move animation.
+ *
+ * @param distance is the total distance in pixels to offset the glyphs when animation
+ * completes. Negative distance means we are animating the position towards the center.
+ * @param fraction fraction of the clock movement. 0 means it is at the beginning, and 1 means
+ * it finished moving.
+ */
+ fun onPositionUpdated(distance: Float, fraction: Float)
+
+ /**
+ * Runs when swiping clock picker, swipingFraction: 1.0 -> clock is scaled up in the preview,
+ * 0.0 -> clock is scaled down in the shade; previewRatio is previewSize / screenSize
+ */
+ fun onPickerCarouselSwiping(swipingFraction: Float)
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockConfig.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockConfig.kt
new file mode 100644
index 000000000000..d84d89087349
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockConfig.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+package com.android.systemui.plugins.clocks
+
+/**
+ * Exposes the rendering capabilities of this clock to SystemUI so that it can be hosted and render
+ * correctly in SystemUI's process. Ideally all clocks could be rendered identically, but in
+ * practice we different clocks require different behavior from SystemUI.
+ */
+data class ClockConfig(
+ val id: ClockId,
+
+ /** Localized name of the clock */
+ val name: String,
+
+ /** Localized accessibility description for the clock */
+ val description: String,
+
+ /** Transition to AOD should move smartspace like large clock instead of small clock */
+ val useAlternateSmartspaceAODTransition: Boolean = false,
+
+ /** Deprecated version of isReactiveToTone; moved to ClockPickerConfig */
+ @Deprecated("TODO(b/352049256): Remove in favor of ClockPickerConfig.isReactiveToTone")
+ val isReactiveToTone: Boolean = true,
+
+ /** True if the clock is large frame clock, which will use weather in compose. */
+ val useCustomClockScene: Boolean = false,
+)
+
+/** Render configuration options for a specific clock face. */
+data class ClockFaceConfig(
+ /** Expected interval between calls to onTimeTick. Can always reduce to PER_MINUTE in AOD. */
+ val tickRate: ClockTickRate = ClockTickRate.PER_MINUTE,
+
+ /** Call to check whether the clock consumes weather data */
+ val hasCustomWeatherDataDisplay: Boolean = false,
+
+ /**
+ * Whether this clock has a custom position update animation. If true, the keyguard will call
+ * `onPositionUpdated` to notify the clock of a position update animation. If false, a default
+ * animation will be used (e.g. a simple translation).
+ */
+ val hasCustomPositionUpdatedAnimation: Boolean = false,
+
+ /** True if the clock is large frame clock, which will use weatherBlueprint in compose. */
+ val useCustomClockScene: Boolean = false,
+)
+
+/** Tick rates for clocks */
+enum class ClockTickRate(val value: Int) {
+ PER_MINUTE(2), // Update the clock once per minute.
+ PER_SECOND(1), // Update the clock once per second.
+ PER_FRAME(0), // Update the clock every second.
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockController.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockController.kt
new file mode 100644
index 000000000000..32fec3277f18
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockController.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+package com.android.systemui.plugins.clocks
+
+import com.android.systemui.plugins.annotations.ProtectedInterface
+import com.android.systemui.plugins.annotations.SimpleProperty
+import java.io.PrintWriter
+
+/** Interface for controlling an active clock */
+@ProtectedInterface
+interface ClockController {
+ @get:SimpleProperty
+ /** A small version of the clock, appropriate for smaller viewports */
+ val smallClock: ClockFaceController
+
+ @get:SimpleProperty
+ /** A large version of the clock, appropriate when a bigger viewport is available */
+ val largeClock: ClockFaceController
+
+ @get:SimpleProperty
+ /** Determines the way the hosting app should behave when rendering either clock face */
+ val config: ClockConfig
+
+ @get:SimpleProperty
+ /** Events that clocks may need to respond to */
+ val events: ClockEvents
+
+ /** Initializes various rendering parameters. If never called, provides reasonable defaults. */
+ fun initialize(isDarkTheme: Boolean, dozeFraction: Float, foldFraction: Float)
+
+ /** Optional method for dumping debug information */
+ fun dump(pw: PrintWriter)
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockEvents.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockEvents.kt
new file mode 100644
index 000000000000..235475f6b202
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockEvents.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+package com.android.systemui.plugins.clocks
+
+import com.android.systemui.plugins.annotations.ProtectedInterface
+import com.android.systemui.plugins.annotations.ProtectedReturn
+import java.util.Locale
+import java.util.TimeZone
+
+/** Events that should call when various rendering parameters change */
+@ProtectedInterface
+interface ClockEvents {
+ @get:ProtectedReturn("return false;")
+ /** Set to enable or disable swipe interaction */
+ var isReactiveTouchInteractionEnabled: Boolean // TODO(b/364664388): Remove/Rename
+
+ /** Call whenever timezone changes */
+ fun onTimeZoneChanged(timeZone: TimeZone)
+
+ /** Call whenever the text time format changes (12hr vs 24hr) */
+ fun onTimeFormatChanged(is24Hr: Boolean)
+
+ /** Call whenever the locale changes */
+ fun onLocaleChanged(locale: Locale)
+
+ /** Call whenever the weather data should update */
+ fun onWeatherDataChanged(data: WeatherData)
+
+ /** Call with alarm information */
+ fun onAlarmDataChanged(data: AlarmData)
+
+ /** Call with zen/dnd information */
+ fun onZenDataChanged(data: ZenData)
+
+ /** Update reactive axes for this clock */
+ fun onFontAxesChanged(axes: List<ClockFontAxisSetting>)
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockFaceController.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockFaceController.kt
new file mode 100644
index 000000000000..8a023f1c2388
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockFaceController.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+package com.android.systemui.plugins.clocks
+
+import android.view.View
+import com.android.systemui.plugins.annotations.ProtectedInterface
+import com.android.systemui.plugins.annotations.SimpleProperty
+
+/** Interface for a specific clock face version rendered by the clock */
+@ProtectedInterface
+interface ClockFaceController {
+ @get:SimpleProperty
+ @Deprecated("Prefer use of layout")
+ /** View that renders the clock face */
+ val view: View
+
+ @get:SimpleProperty
+ /** Layout specification for this clock */
+ val layout: ClockFaceLayout
+
+ @get:SimpleProperty
+ /** Determines the way the hosting app should behave when rendering this clock face */
+ val config: ClockFaceConfig
+
+ @get:SimpleProperty
+ /** Current theme information the clock is using */
+ val theme: ThemeConfig
+
+ @get:SimpleProperty
+ /** Events specific to this clock face */
+ val events: ClockFaceEvents
+
+ @get:SimpleProperty
+ /** Triggers for various animations */
+ val animations: ClockAnimations
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockFaceEvents.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockFaceEvents.kt
new file mode 100644
index 000000000000..029e54658f60
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockFaceEvents.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+package com.android.systemui.plugins.clocks
+
+import android.graphics.Rect
+import com.android.systemui.plugins.annotations.ProtectedInterface
+
+/** Events that have specific data about the related face */
+@ProtectedInterface
+interface ClockFaceEvents {
+ /** Call every tick to update the rendered time */
+ fun onTimeTick()
+
+ /**
+ * Call whenever the theme or seedColor is updated
+ *
+ * Theme can be specific to the clock face.
+ * - isDarkTheme -> clock should be light
+ * - !isDarkTheme -> clock should be dark
+ */
+ fun onThemeChanged(theme: ThemeConfig)
+
+ /**
+ * Call whenever font settings change. Pass in a target font size in pixels. The specific clock
+ * design is allowed to ignore this target size on a case-by-case basis.
+ */
+ fun onFontSettingChanged(fontSizePx: Float)
+
+ /**
+ * Target region information for the clock face. For small clock, this will match the bounds of
+ * the parent view mostly, but have a target height based on the height of the default clock.
+ * For large clocks, the parent view is the entire device size, but most clocks will want to
+ * render within the centered targetRect to avoid obstructing other elements. The specified
+ * targetRegion is relative to the parent view.
+ */
+ fun onTargetRegionChanged(targetRegion: Rect?)
+
+ /** Called to notify the clock about its display. */
+ fun onSecondaryDisplayChanged(onSecondaryDisplay: Boolean)
+}
+
+/** Contains Theming information for the clock face */
+data class ThemeConfig(
+ /** True if the clock should use dark theme (light text on dark background) */
+ val isDarkTheme: Boolean,
+
+ /**
+ * A clock specific seed color to use when theming, if any was specified by the user. A null
+ * value denotes that we should use the seed color for the current system theme.
+ */
+ val seedColor: Int?,
+)
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockFaceLayout.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockFaceLayout.kt
new file mode 100644
index 000000000000..fb5ef02aa06a
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockFaceLayout.kt
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+package com.android.systemui.plugins.clocks
+
+import android.content.Context
+import android.util.DisplayMetrics
+import android.view.View
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
+import androidx.constraintlayout.widget.ConstraintSet.END
+import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
+import androidx.constraintlayout.widget.ConstraintSet.START
+import androidx.constraintlayout.widget.ConstraintSet.TOP
+import androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT
+import com.android.internal.policy.SystemBarUtils
+import com.android.systemui.plugins.annotations.GeneratedImport
+import com.android.systemui.plugins.annotations.ProtectedInterface
+import com.android.systemui.plugins.annotations.ProtectedReturn
+
+/** Specifies layout information for the clock face */
+@ProtectedInterface
+@GeneratedImport("java.util.ArrayList")
+@GeneratedImport("android.view.View")
+interface ClockFaceLayout {
+ @get:ProtectedReturn("return new ArrayList<View>();")
+ /** All clock views to add to the root constraint layout before applying constraints. */
+ val views: List<View>
+
+ @ProtectedReturn("return constraints;")
+ /** Custom constraints to apply to Lockscreen ConstraintLayout. */
+ fun applyConstraints(constraints: ConstraintSet): ConstraintSet
+
+ @ProtectedReturn("return constraints;")
+ /** Custom constraints to apply to preview ConstraintLayout. */
+ fun applyPreviewConstraints(
+ clockPreviewConfig: ClockPreviewConfig,
+ constraints: ConstraintSet,
+ ): ConstraintSet
+
+ /** Apply specified AOD BurnIn parameters to this layout */
+ fun applyAodBurnIn(aodBurnInModel: AodClockBurnInModel)
+}
+
+/** Data class to contain AOD BurnIn information for correct aod rendering */
+data class AodClockBurnInModel(
+ /** Scale that the clock should render at to mitigate burnin */
+ val scale: Float,
+
+ /** X-Translation for the clock to mitigate burnin */
+ val translationX: Float,
+
+ /** Y-Translation for the clock to mitigate burnin */
+ val translationY: Float,
+)
+
+/** A ClockFaceLayout that applies the default lockscreen layout to a single view */
+class DefaultClockFaceLayout(val view: View) : ClockFaceLayout {
+ override val views = listOf(view)
+
+ override fun applyConstraints(constraints: ConstraintSet): ConstraintSet {
+ if (views.size != 1) {
+ throw IllegalArgumentException(
+ "Should have only one container view when using DefaultClockFaceLayout"
+ )
+ }
+ return constraints
+ }
+
+ override fun applyPreviewConstraints(
+ clockPreviewConfig: ClockPreviewConfig,
+ constraints: ConstraintSet,
+ ): ConstraintSet {
+ return applyDefaultPreviewConstraints(clockPreviewConfig, constraints)
+ }
+
+ override fun applyAodBurnIn(aodBurnInModel: AodClockBurnInModel) {
+ // Default clock doesn't need detailed control of view
+ }
+
+ companion object {
+ fun applyDefaultPreviewConstraints(
+ clockPreviewConfig: ClockPreviewConfig,
+ constraints: ConstraintSet,
+ ): ConstraintSet {
+ constraints.apply {
+ val context = clockPreviewConfig.previewContext
+ val lockscreenClockViewLargeId = getId(context, "lockscreen_clock_view_large")
+ constrainWidth(lockscreenClockViewLargeId, WRAP_CONTENT)
+ constrainHeight(lockscreenClockViewLargeId, WRAP_CONTENT)
+ constrainMaxHeight(lockscreenClockViewLargeId, 0)
+
+ val largeClockTopMargin =
+ SystemBarUtils.getStatusBarHeight(context) +
+ getDimen(context, "small_clock_padding_top") +
+ getDimen(context, "keyguard_smartspace_top_offset") +
+ getDimen(context, "date_weather_view_height") +
+ getDimen(context, "enhanced_smartspace_height")
+ connect(lockscreenClockViewLargeId, TOP, PARENT_ID, TOP, largeClockTopMargin)
+ connect(lockscreenClockViewLargeId, START, PARENT_ID, START)
+ connect(lockscreenClockViewLargeId, END, PARENT_ID, END)
+
+ // In preview, we'll show UDFPS icon for UDFPS devices
+ // and nothing for non-UDFPS devices,
+ // and we're not planning to add this vide in clockHostView
+ // so we only need position of device entry icon to constrain clock
+ // Copied calculation codes from applyConstraints in DefaultDeviceEntrySection
+ val bottomPaddingPx = getDimen(context, "lock_icon_margin_bottom")
+ val defaultDensity =
+ DisplayMetrics.DENSITY_DEVICE_STABLE.toFloat() /
+ DisplayMetrics.DENSITY_DEFAULT.toFloat()
+ val lockIconRadiusPx = (defaultDensity * 36).toInt()
+ val clockBottomMargin = bottomPaddingPx + 2 * lockIconRadiusPx
+
+ connect(lockscreenClockViewLargeId, BOTTOM, PARENT_ID, BOTTOM, clockBottomMargin)
+ val smallClockViewId = getId(context, "lockscreen_clock_view")
+ constrainWidth(smallClockViewId, WRAP_CONTENT)
+ constrainHeight(smallClockViewId, getDimen(context, "small_clock_height"))
+ connect(
+ smallClockViewId,
+ START,
+ PARENT_ID,
+ START,
+ getDimen(context, "clock_padding_start") +
+ getDimen(context, "status_view_margin_horizontal"),
+ )
+ val smallClockTopMargin =
+ getSmallClockTopPadding(
+ clockPreviewConfig = clockPreviewConfig,
+ SystemBarUtils.getStatusBarHeight(context),
+ )
+ connect(smallClockViewId, TOP, PARENT_ID, TOP, smallClockTopMargin)
+ }
+ return constraints
+ }
+
+ fun getId(context: Context, name: String): Int {
+ val packageName = context.packageName
+ val res = context.packageManager.getResourcesForApplication(packageName)
+ val id = res.getIdentifier(name, "id", packageName)
+ return id
+ }
+
+ fun getDimen(context: Context, name: String): Int {
+ val packageName = context.packageName
+ val res = context.resources
+ val id = res.getIdentifier(name, "dimen", packageName)
+ return if (id == 0) 0 else res.getDimensionPixelSize(id)
+ }
+
+ fun getSmallClockTopPadding(
+ clockPreviewConfig: ClockPreviewConfig,
+ statusBarHeight: Int,
+ ): Int {
+ return if (clockPreviewConfig.isShadeLayoutWide) {
+ getDimen(clockPreviewConfig.previewContext, "keyguard_split_shade_top_margin") -
+ if (clockPreviewConfig.isSceneContainerFlagEnabled) statusBarHeight else 0
+ } else {
+ getDimen(clockPreviewConfig.previewContext, "keyguard_clock_top_margin") +
+ if (!clockPreviewConfig.isSceneContainerFlagEnabled) statusBarHeight else 0
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockMessageBuffers.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockMessageBuffers.kt
new file mode 100644
index 000000000000..bec589ae139e
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockMessageBuffers.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+package com.android.systemui.plugins.clocks
+
+import com.android.systemui.log.core.MessageBuffer
+
+/** MessageBuffers for clocks that want to log information to SystemUI dumps */
+data class ClockMessageBuffers(
+ /** Message buffer for general infrastructure */
+ val infraMessageBuffer: MessageBuffer,
+
+ /** Message buffer for small clock rendering */
+ val smallClockMessageBuffer: MessageBuffer,
+
+ /** Message buffer for large clock rendering */
+ val largeClockMessageBuffer: MessageBuffer,
+) {
+ constructor(buffer: MessageBuffer) : this(buffer, buffer, buffer) {}
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockPickerConfig.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockPickerConfig.kt
new file mode 100644
index 000000000000..1bc9367ce3c5
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockPickerConfig.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+package com.android.systemui.plugins.clocks
+
+import android.graphics.drawable.Drawable
+
+data class ClockPickerConfig
+@JvmOverloads
+constructor(
+ val id: String,
+
+ /** Localized name of the clock */
+ val name: String,
+
+ /** Localized accessibility description for the clock */
+ val description: String,
+
+ /* Static & lightweight thumbnail version of the clock */
+ val thumbnail: Drawable,
+
+ /** True if the clock will react to tone changes in the seed color */
+ val isReactiveToTone: Boolean = true,
+
+ /** Font axes that can be modified on this clock */
+ val axes: List<ClockFontAxis> = listOf(),
+)
+
+/** Represents an Axis that can be modified */
+data class ClockFontAxis(
+ /** Axis key, not user renderable */
+ val key: String,
+
+ /** Intended mode of user interaction */
+ val type: AxisType,
+
+ /** Maximum value the axis supports */
+ val maxValue: Float,
+
+ /** Minimum value the axis supports */
+ val minValue: Float,
+
+ /** Current value the axis is set to */
+ val currentValue: Float,
+
+ /** User-renderable name of the axis */
+ val name: String,
+
+ /** Description of the axis */
+ val description: String,
+) {
+ fun toSetting() = ClockFontAxisSetting(key, currentValue)
+
+ companion object {
+ fun merge(
+ fontAxes: List<ClockFontAxis>,
+ axisSettings: List<ClockFontAxisSetting>,
+ ): List<ClockFontAxis> {
+ val result = mutableListOf<ClockFontAxis>()
+ for (axis in fontAxes) {
+ val setting = axisSettings.firstOrNull { axis.key == it.key }
+ val output = setting?.let { axis.copy(currentValue = it.value) } ?: axis
+ result.add(output)
+ }
+ return result
+ }
+ }
+}
+
+/** Axis user interaction modes */
+enum class AxisType {
+ /** Continuous range between minValue & maxValue. */
+ Float,
+
+ /** Only minValue & maxValue are valid. No intermediate values between them are allowed. */
+ Boolean,
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
index 8ea5725b3509..7426f061b84c 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
@@ -13,35 +13,11 @@
*/
package com.android.systemui.plugins.clocks
-import android.content.Context
-import android.graphics.Rect
-import android.graphics.drawable.Drawable
-import android.util.DisplayMetrics
-import android.view.View
-import androidx.constraintlayout.widget.ConstraintSet
-import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
-import androidx.constraintlayout.widget.ConstraintSet.END
-import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
-import androidx.constraintlayout.widget.ConstraintSet.START
-import androidx.constraintlayout.widget.ConstraintSet.TOP
-import androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT
-import com.android.internal.annotations.Keep
-import com.android.internal.policy.SystemBarUtils
-import com.android.systemui.log.core.MessageBuffer
import com.android.systemui.plugins.Plugin
import com.android.systemui.plugins.annotations.GeneratedImport
import com.android.systemui.plugins.annotations.ProtectedInterface
import com.android.systemui.plugins.annotations.ProtectedReturn
import com.android.systemui.plugins.annotations.ProvidesInterface
-import com.android.systemui.plugins.annotations.SimpleProperty
-import java.io.PrintWriter
-import java.util.Locale
-import java.util.TimeZone
-import org.json.JSONArray
-import org.json.JSONObject
-
-/** Identifies a clock design */
-typealias ClockId = String
/** A Plugin which exposes the ClockProvider interface */
@ProtectedInterface
@@ -74,528 +50,8 @@ interface ClockProvider {
fun getClockPickerConfig(settings: ClockSettings): ClockPickerConfig
}
-/** Interface for controlling an active clock */
-@ProtectedInterface
-interface ClockController {
- @get:SimpleProperty
- /** A small version of the clock, appropriate for smaller viewports */
- val smallClock: ClockFaceController
-
- @get:SimpleProperty
- /** A large version of the clock, appropriate when a bigger viewport is available */
- val largeClock: ClockFaceController
-
- @get:SimpleProperty
- /** Determines the way the hosting app should behave when rendering either clock face */
- val config: ClockConfig
-
- @get:SimpleProperty
- /** Events that clocks may need to respond to */
- val events: ClockEvents
-
- /** Initializes various rendering parameters. If never called, provides reasonable defaults. */
- fun initialize(isDarkTheme: Boolean, dozeFraction: Float, foldFraction: Float)
-
- /** Optional method for dumping debug information */
- fun dump(pw: PrintWriter)
-}
-
-/** Interface for a specific clock face version rendered by the clock */
-@ProtectedInterface
-interface ClockFaceController {
- @get:SimpleProperty
- @Deprecated("Prefer use of layout")
- /** View that renders the clock face */
- val view: View
-
- @get:SimpleProperty
- /** Layout specification for this clock */
- val layout: ClockFaceLayout
-
- @get:SimpleProperty
- /** Determines the way the hosting app should behave when rendering this clock face */
- val config: ClockFaceConfig
-
- @get:SimpleProperty
- /** Current theme information the clock is using */
- val theme: ThemeConfig
-
- @get:SimpleProperty
- /** Events specific to this clock face */
- val events: ClockFaceEvents
-
- @get:SimpleProperty
- /** Triggers for various animations */
- val animations: ClockAnimations
-}
-
-/** For clocks that want to report debug information */
-data class ClockMessageBuffers(
- /** Message buffer for general infra */
- val infraMessageBuffer: MessageBuffer,
-
- /** Message buffer for small clock renering */
- val smallClockMessageBuffer: MessageBuffer,
-
- /** Message buffer for large clock rendering */
- val largeClockMessageBuffer: MessageBuffer,
-) {
- constructor(buffer: MessageBuffer) : this(buffer, buffer, buffer) {}
-}
-
-data class AodClockBurnInModel(val scale: Float, val translationX: Float, val translationY: Float)
-
-/** Specifies layout information for the clock face */
-@ProtectedInterface
-@GeneratedImport("java.util.ArrayList")
-@GeneratedImport("android.view.View")
-interface ClockFaceLayout {
- @get:ProtectedReturn("return new ArrayList<View>();")
- /** All clock views to add to the root constraint layout before applying constraints. */
- val views: List<View>
-
- @ProtectedReturn("return constraints;")
- /** Custom constraints to apply to Lockscreen ConstraintLayout. */
- fun applyConstraints(constraints: ConstraintSet): ConstraintSet
-
- @ProtectedReturn("return constraints;")
- /** Custom constraints to apply to preview ConstraintLayout. */
- fun applyPreviewConstraints(
- clockPreviewConfig: ClockPreviewConfig,
- constraints: ConstraintSet,
- ): ConstraintSet
-
- fun applyAodBurnIn(aodBurnInModel: AodClockBurnInModel)
-}
-
-/** A ClockFaceLayout that applies the default lockscreen layout to a single view */
-class DefaultClockFaceLayout(val view: View) : ClockFaceLayout {
- // both small and large clock should have a container (RelativeLayout in
- // SimpleClockFaceController)
- override val views = listOf(view)
-
- override fun applyConstraints(constraints: ConstraintSet): ConstraintSet {
- if (views.size != 1) {
- throw IllegalArgumentException(
- "Should have only one container view when using DefaultClockFaceLayout"
- )
- }
- return constraints
- }
-
- override fun applyPreviewConstraints(
- clockPreviewConfig: ClockPreviewConfig,
- constraints: ConstraintSet,
- ): ConstraintSet {
- return applyDefaultPreviewConstraints(clockPreviewConfig, constraints)
- }
-
- override fun applyAodBurnIn(aodBurnInModel: AodClockBurnInModel) {
- // Default clock doesn't need detailed control of view
- }
-
- companion object {
- fun applyDefaultPreviewConstraints(
- clockPreviewConfig: ClockPreviewConfig,
- constraints: ConstraintSet,
- ): ConstraintSet {
- constraints.apply {
- val context = clockPreviewConfig.previewContext
- val lockscreenClockViewLargeId = getId(context, "lockscreen_clock_view_large")
- constrainWidth(lockscreenClockViewLargeId, WRAP_CONTENT)
- constrainHeight(lockscreenClockViewLargeId, WRAP_CONTENT)
- constrainMaxHeight(lockscreenClockViewLargeId, 0)
-
- val largeClockTopMargin =
- SystemBarUtils.getStatusBarHeight(context) +
- getDimen(context, "small_clock_padding_top") +
- getDimen(context, "keyguard_smartspace_top_offset") +
- getDimen(context, "date_weather_view_height") +
- getDimen(context, "enhanced_smartspace_height")
- connect(lockscreenClockViewLargeId, TOP, PARENT_ID, TOP, largeClockTopMargin)
- connect(lockscreenClockViewLargeId, START, PARENT_ID, START)
- connect(lockscreenClockViewLargeId, END, PARENT_ID, END)
-
- // In preview, we'll show UDFPS icon for UDFPS devices
- // and nothing for non-UDFPS devices,
- // and we're not planning to add this vide in clockHostView
- // so we only need position of device entry icon to constrain clock
- // Copied calculation codes from applyConstraints in DefaultDeviceEntrySection
- val bottomPaddingPx = getDimen(context, "lock_icon_margin_bottom")
- val defaultDensity =
- DisplayMetrics.DENSITY_DEVICE_STABLE.toFloat() /
- DisplayMetrics.DENSITY_DEFAULT.toFloat()
- val lockIconRadiusPx = (defaultDensity * 36).toInt()
- val clockBottomMargin = bottomPaddingPx + 2 * lockIconRadiusPx
-
- connect(lockscreenClockViewLargeId, BOTTOM, PARENT_ID, BOTTOM, clockBottomMargin)
- val smallClockViewId = getId(context, "lockscreen_clock_view")
- constrainWidth(smallClockViewId, WRAP_CONTENT)
- constrainHeight(smallClockViewId, getDimen(context, "small_clock_height"))
- connect(
- smallClockViewId,
- START,
- PARENT_ID,
- START,
- getDimen(context, "clock_padding_start") +
- getDimen(context, "status_view_margin_horizontal"),
- )
- val smallClockTopMargin =
- getSmallClockTopPadding(
- clockPreviewConfig = clockPreviewConfig,
- SystemBarUtils.getStatusBarHeight(context),
- )
- connect(smallClockViewId, TOP, PARENT_ID, TOP, smallClockTopMargin)
- }
- return constraints
- }
-
- fun getId(context: Context, name: String): Int {
- val packageName = context.packageName
- val res = context.packageManager.getResourcesForApplication(packageName)
- val id = res.getIdentifier(name, "id", packageName)
- return id
- }
-
- fun getDimen(context: Context, name: String): Int {
- val packageName = context.packageName
- val res = context.resources
- val id = res.getIdentifier(name, "dimen", packageName)
- return if (id == 0) 0 else res.getDimensionPixelSize(id)
- }
-
- fun getSmallClockTopPadding(
- clockPreviewConfig: ClockPreviewConfig,
- statusBarHeight: Int,
- ): Int {
- return if (clockPreviewConfig.isShadeLayoutWide) {
- getDimen(clockPreviewConfig.previewContext, "keyguard_split_shade_top_margin") -
- if (clockPreviewConfig.isSceneContainerFlagEnabled) statusBarHeight else 0
- } else {
- getDimen(clockPreviewConfig.previewContext, "keyguard_clock_top_margin") +
- if (!clockPreviewConfig.isSceneContainerFlagEnabled) statusBarHeight else 0
- }
- }
- }
-}
-
-/** Events that should call when various rendering parameters change */
-@ProtectedInterface
-interface ClockEvents {
- @get:ProtectedReturn("return false;")
- /** Set to enable or disable swipe interaction */
- var isReactiveTouchInteractionEnabled: Boolean // TODO(b/364664388): Remove/Rename
-
- /** Call whenever timezone changes */
- fun onTimeZoneChanged(timeZone: TimeZone)
-
- /** Call whenever the text time format changes (12hr vs 24hr) */
- fun onTimeFormatChanged(is24Hr: Boolean)
-
- /** Call whenever the locale changes */
- fun onLocaleChanged(locale: Locale)
-
- /** Call whenever the weather data should update */
- fun onWeatherDataChanged(data: WeatherData)
-
- /** Call with alarm information */
- fun onAlarmDataChanged(data: AlarmData)
-
- /** Call with zen/dnd information */
- fun onZenDataChanged(data: ZenData)
-
- /** Update reactive axes for this clock */
- fun onFontAxesChanged(axes: List<ClockFontAxisSetting>)
-}
-
-/** Axis setting value for a clock */
-data class ClockFontAxisSetting(
- /** Axis key; matches ClockFontAxis.key */
- val key: String,
-
- /** Value to set this axis to */
- val value: Float,
-) {
- companion object {
- private val KEY_AXIS_KEY = "key"
- private val KEY_AXIS_VALUE = "value"
-
- fun toJson(setting: ClockFontAxisSetting): JSONObject {
- return JSONObject().apply {
- put(KEY_AXIS_KEY, setting.key)
- put(KEY_AXIS_VALUE, setting.value)
- }
- }
-
- fun toJson(settings: List<ClockFontAxisSetting>): JSONArray {
- return JSONArray().apply {
- for (axis in settings) {
- put(toJson(axis))
- }
- }
- }
-
- fun fromJson(jsonObj: JSONObject): ClockFontAxisSetting {
- return ClockFontAxisSetting(
- key = jsonObj.getString(KEY_AXIS_KEY),
- value = jsonObj.getDouble(KEY_AXIS_VALUE).toFloat(),
- )
- }
-
- fun fromJson(jsonArray: JSONArray): List<ClockFontAxisSetting> {
- val result = mutableListOf<ClockFontAxisSetting>()
- for (i in 0..jsonArray.length() - 1) {
- val obj = jsonArray.getJSONObject(i)
- if (obj == null) continue
- result.add(fromJson(obj))
- }
- return result
- }
-
- fun toFVar(settings: List<ClockFontAxisSetting>): String {
- val sb = StringBuilder()
- for (axis in settings) {
- if (sb.length > 0) sb.append(", ")
- sb.append("'${axis.key}' ${axis.value.toInt()}")
- }
- return sb.toString()
- }
- }
-}
-
-/** Methods which trigger various clock animations */
-@ProtectedInterface
-interface ClockAnimations {
- /** Runs an enter animation (if any) */
- fun enter()
-
- /** Sets how far into AOD the device currently is. */
- fun doze(fraction: Float)
-
- /** Sets how far into the folding animation the device is. */
- fun fold(fraction: Float)
-
- /** Runs the battery animation (if any). */
- fun charge()
-
- /**
- * Runs when the clock's position changed during the move animation.
- *
- * @param fromLeft the [View.getLeft] position of the clock, before it started moving.
- * @param direction the direction in which it is moving. A positive number means right, and
- * negative means left.
- * @param fraction fraction of the clock movement. 0 means it is at the beginning, and 1 means
- * it finished moving.
- * @deprecated use {@link #onPositionUpdated(float, float)} instead.
- */
- fun onPositionUpdated(fromLeft: Int, direction: Int, fraction: Float)
-
- /**
- * Runs when the clock's position changed during the move animation.
- *
- * @param distance is the total distance in pixels to offset the glyphs when animation
- * completes. Negative distance means we are animating the position towards the center.
- * @param fraction fraction of the clock movement. 0 means it is at the beginning, and 1 means
- * it finished moving.
- */
- fun onPositionUpdated(distance: Float, fraction: Float)
-
- /**
- * Runs when swiping clock picker, swipingFraction: 1.0 -> clock is scaled up in the preview,
- * 0.0 -> clock is scaled down in the shade; previewRatio is previewSize / screenSize
- */
- fun onPickerCarouselSwiping(swipingFraction: Float)
-}
-
-/** Events that have specific data about the related face */
-@ProtectedInterface
-interface ClockFaceEvents {
- /** Call every time tick */
- fun onTimeTick()
-
- /**
- * Call whenever the theme or seedColor is updated
- *
- * Theme can be specific to the clock face.
- * - isDarkTheme -> clock should be light
- * - !isDarkTheme -> clock should be dark
- */
- fun onThemeChanged(theme: ThemeConfig)
-
- /**
- * Call whenever font settings change. Pass in a target font size in pixels. The specific clock
- * design is allowed to ignore this target size on a case-by-case basis.
- */
- fun onFontSettingChanged(fontSizePx: Float)
-
- /**
- * Target region information for the clock face. For small clock, this will match the bounds of
- * the parent view mostly, but have a target height based on the height of the default clock.
- * For large clocks, the parent view is the entire device size, but most clocks will want to
- * render within the centered targetRect to avoid obstructing other elements. The specified
- * targetRegion is relative to the parent view.
- */
- fun onTargetRegionChanged(targetRegion: Rect?)
-
- /** Called to notify the clock about its display. */
- fun onSecondaryDisplayChanged(onSecondaryDisplay: Boolean)
-}
-
-data class ThemeConfig(val isDarkTheme: Boolean, val seedColor: Int?)
-
-/** Tick rates for clocks */
-enum class ClockTickRate(val value: Int) {
- PER_MINUTE(2), // Update the clock once per minute.
- PER_SECOND(1), // Update the clock once per second.
- PER_FRAME(0), // Update the clock every second.
-}
+/** Identifies a clock design */
+typealias ClockId = String
/** Some data about a clock design */
data class ClockMetadata(val clockId: ClockId)
-
-data class ClockPickerConfig
-@JvmOverloads
-constructor(
- val id: String,
-
- /** Localized name of the clock */
- val name: String,
-
- /** Localized accessibility description for the clock */
- val description: String,
-
- /* Static & lightweight thumbnail version of the clock */
- val thumbnail: Drawable,
-
- /** True if the clock will react to tone changes in the seed color */
- val isReactiveToTone: Boolean = true,
-
- /** Font axes that can be modified on this clock */
- val axes: List<ClockFontAxis> = listOf(),
-)
-
-/** Represents an Axis that can be modified */
-data class ClockFontAxis(
- /** Axis key, not user renderable */
- val key: String,
-
- /** Intended mode of user interaction */
- val type: AxisType,
-
- /** Maximum value the axis supports */
- val maxValue: Float,
-
- /** Minimum value the axis supports */
- val minValue: Float,
-
- /** Current value the axis is set to */
- val currentValue: Float,
-
- /** User-renderable name of the axis */
- val name: String,
-
- /** Description of the axis */
- val description: String,
-) {
- fun toSetting() = ClockFontAxisSetting(key, currentValue)
-
- companion object {
- fun merge(
- fontAxes: List<ClockFontAxis>,
- axisSettings: List<ClockFontAxisSetting>,
- ): List<ClockFontAxis> {
- val result = mutableListOf<ClockFontAxis>()
- for (axis in fontAxes) {
- val setting = axisSettings.firstOrNull { axis.key == it.key }
- val output = setting?.let { axis.copy(currentValue = it.value) } ?: axis
- result.add(output)
- }
- return result
- }
- }
-}
-
-/** Axis user interaction modes */
-enum class AxisType {
- /** Continuous range between minValue & maxValue. */
- Float,
-
- /** Only minValue & maxValue are valid. No intermediate values between them are allowed. */
- Boolean,
-}
-
-/** Render configuration for the full clock. Modifies the way systemUI behaves with this clock. */
-data class ClockConfig(
- val id: String,
-
- /** Localized name of the clock */
- val name: String,
-
- /** Localized accessibility description for the clock */
- val description: String,
-
- /** Transition to AOD should move smartspace like large clock instead of small clock */
- val useAlternateSmartspaceAODTransition: Boolean = false,
-
- /** Deprecated version of isReactiveToTone; moved to ClockPickerConfig */
- @Deprecated("TODO(b/352049256): Remove in favor of ClockPickerConfig.isReactiveToTone")
- val isReactiveToTone: Boolean = true,
-
- /** True if the clock is large frame clock, which will use weather in compose. */
- val useCustomClockScene: Boolean = false,
-)
-
-/** Render configuration options for a clock face. Modifies the way SystemUI behaves. */
-data class ClockFaceConfig(
- /** Expected interval between calls to onTimeTick. Can always reduce to PER_MINUTE in AOD. */
- val tickRate: ClockTickRate = ClockTickRate.PER_MINUTE,
-
- /** Call to check whether the clock consumes weather data */
- val hasCustomWeatherDataDisplay: Boolean = false,
-
- /**
- * Whether this clock has a custom position update animation. If true, the keyguard will call
- * `onPositionUpdated` to notify the clock of a position update animation. If false, a default
- * animation will be used (e.g. a simple translation).
- */
- val hasCustomPositionUpdatedAnimation: Boolean = false,
-
- /** True if the clock is large frame clock, which will use weatherBlueprint in compose. */
- val useCustomClockScene: Boolean = false,
-)
-
-/** Structure for keeping clock-specific settings */
-@Keep
-data class ClockSettings(
- val clockId: ClockId? = null,
- val seedColor: Int? = null,
- val axes: List<ClockFontAxisSetting> = listOf(),
-) {
- // Exclude metadata from equality checks
- var metadata: JSONObject = JSONObject()
-
- companion object {
- private val KEY_CLOCK_ID = "clockId"
- private val KEY_SEED_COLOR = "seedColor"
- private val KEY_METADATA = "metadata"
- private val KEY_AXIS_LIST = "axes"
-
- fun toJson(setting: ClockSettings): JSONObject {
- return JSONObject().apply {
- put(KEY_CLOCK_ID, setting.clockId)
- put(KEY_SEED_COLOR, setting.seedColor)
- put(KEY_METADATA, setting.metadata)
- put(KEY_AXIS_LIST, ClockFontAxisSetting.toJson(setting.axes))
- }
- }
-
- fun fromJson(json: JSONObject): ClockSettings {
- val clockId = if (!json.isNull(KEY_CLOCK_ID)) json.getString(KEY_CLOCK_ID) else null
- val seedColor = if (!json.isNull(KEY_SEED_COLOR)) json.getInt(KEY_SEED_COLOR) else null
- val axisList = json.optJSONArray(KEY_AXIS_LIST)?.let(ClockFontAxisSetting::fromJson)
- return ClockSettings(clockId, seedColor, axisList ?: listOf()).apply {
- metadata = json.optJSONObject(KEY_METADATA) ?: JSONObject()
- }
- }
- }
-}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockSettings.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockSettings.kt
new file mode 100644
index 000000000000..6128c00f3843
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockSettings.kt
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+package com.android.systemui.plugins.clocks
+
+import com.android.internal.annotations.Keep
+import org.json.JSONArray
+import org.json.JSONObject
+
+@Keep
+/** Structure for keeping clock-specific settings */
+data class ClockSettings(
+ val clockId: ClockId? = null,
+ val seedColor: Int? = null,
+ val axes: List<ClockFontAxisSetting> = listOf(),
+) {
+ // Exclude metadata from equality checks
+ var metadata: JSONObject = JSONObject()
+
+ companion object {
+ private val KEY_CLOCK_ID = "clockId"
+ private val KEY_SEED_COLOR = "seedColor"
+ private val KEY_METADATA = "metadata"
+ private val KEY_AXIS_LIST = "axes"
+
+ fun toJson(setting: ClockSettings): JSONObject {
+ return JSONObject().apply {
+ put(KEY_CLOCK_ID, setting.clockId)
+ put(KEY_SEED_COLOR, setting.seedColor)
+ put(KEY_METADATA, setting.metadata)
+ put(KEY_AXIS_LIST, ClockFontAxisSetting.toJson(setting.axes))
+ }
+ }
+
+ fun fromJson(json: JSONObject): ClockSettings {
+ val clockId = if (!json.isNull(KEY_CLOCK_ID)) json.getString(KEY_CLOCK_ID) else null
+ val seedColor = if (!json.isNull(KEY_SEED_COLOR)) json.getInt(KEY_SEED_COLOR) else null
+ val axisList = json.optJSONArray(KEY_AXIS_LIST)?.let(ClockFontAxisSetting::fromJson)
+ return ClockSettings(clockId, seedColor, axisList ?: listOf()).apply {
+ metadata = json.optJSONObject(KEY_METADATA) ?: JSONObject()
+ }
+ }
+ }
+}
+
+@Keep
+/** Axis setting value for a clock */
+data class ClockFontAxisSetting(
+ /** Axis key; matches ClockFontAxis.key */
+ val key: String,
+
+ /** Value to set this axis to */
+ val value: Float,
+) {
+ companion object {
+ private val KEY_AXIS_KEY = "key"
+ private val KEY_AXIS_VALUE = "value"
+
+ fun toJson(setting: ClockFontAxisSetting): JSONObject {
+ return JSONObject().apply {
+ put(KEY_AXIS_KEY, setting.key)
+ put(KEY_AXIS_VALUE, setting.value)
+ }
+ }
+
+ fun toJson(settings: List<ClockFontAxisSetting>): JSONArray {
+ return JSONArray().apply {
+ for (axis in settings) {
+ put(toJson(axis))
+ }
+ }
+ }
+
+ fun fromJson(jsonObj: JSONObject): ClockFontAxisSetting {
+ return ClockFontAxisSetting(
+ key = jsonObj.getString(KEY_AXIS_KEY),
+ value = jsonObj.getDouble(KEY_AXIS_VALUE).toFloat(),
+ )
+ }
+
+ fun fromJson(jsonArray: JSONArray): List<ClockFontAxisSetting> {
+ val result = mutableListOf<ClockFontAxisSetting>()
+ for (i in 0..jsonArray.length() - 1) {
+ val obj = jsonArray.getJSONObject(i)
+ if (obj == null) continue
+ result.add(fromJson(obj))
+ }
+ return result
+ }
+
+ fun toFVar(settings: List<ClockFontAxisSetting>): String {
+ val sb = StringBuilder()
+ for (axis in settings) {
+ if (sb.length > 0) sb.append(", ")
+ sb.append("'${axis.key}' ${axis.value.toInt()}")
+ }
+ return sb.toString()
+ }
+ }
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
index 73626b457dcf..e3cbd6643e5f 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
@@ -120,6 +120,11 @@ public interface QSTile {
*/
boolean isListening();
+ /**
+ * Return this tile's {@link TileDetailsViewModel} to be used to render the TileDetailsView.
+ */
+ default TileDetailsViewModel getDetailsViewModel() { return null; }
+
@ProvidesInterface(version = Callback.VERSION)
interface Callback {
static final int VERSION = 2;
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/TileDetailsViewModel.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/TileDetailsViewModel.kt
new file mode 100644
index 000000000000..eab7d7913129
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/TileDetailsViewModel.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.plugins.qs
+
+import androidx.compose.runtime.Composable
+
+/**
+ * The base view model class for rendering the Tile's TileDetailsView.
+ */
+abstract class TileDetailsViewModel {
+
+ // The view content of this tile details view.
+ @Composable
+ abstract fun GetContentView()
+
+ // The callback when the settings button is clicked. Currently this is the same as the on tile
+ // long press callback
+ abstract fun clickOnSettingsButton()
+
+ abstract fun getTitle(): String
+
+ abstract fun getSubTitle(): String
+}
diff --git a/packages/SystemUI/res/layout/audio_sharing_dialog.xml b/packages/SystemUI/res/layout/audio_sharing_dialog.xml
index 7534e159beb0..014b7f7b45df 100644
--- a/packages/SystemUI/res/layout/audio_sharing_dialog.xml
+++ b/packages/SystemUI/res/layout/audio_sharing_dialog.xml
@@ -84,7 +84,7 @@
android:id="@+id/share_audio_button"
style="@style/SettingsLibActionButton"
android:textColor="?androidprv:attr/textColorOnAccent"
- android:background="@drawable/audio_sharing_rounded_bg_ripple"
+ android:background="@drawable/audio_sharing_rounded_bg_ripple_top"
android:layout_marginBottom="4dp"
android:layout_width="0dp"
android:layout_height="wrap_content"
@@ -101,7 +101,7 @@
android:id="@+id/switch_active_button"
style="@style/SettingsLibActionButton"
android:textColor="?androidprv:attr/textColorOnAccent"
- android:background="@drawable/audio_sharing_rounded_bg_ripple"
+ android:background="@drawable/audio_sharing_rounded_bg_ripple_bottom"
android:layout_marginBottom="20dp"
android:layout_width="0dp"
android:layout_height="wrap_content"
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 713aa47970ee..731c4ef463fd 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -753,7 +753,7 @@
<!-- QuickSettings: Bluetooth dialog device in audio sharing default summary [CHAR LIMIT=50]-->
<string name="quick_settings_bluetooth_device_audio_sharing">Audio Sharing</string>
<!-- QuickSettings: Bluetooth dialog device summary for devices that are capable of audio sharing and switching to active[CHAR LIMIT=NONE]-->
- <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active">Tap to switch or share audio</string>
+ <string name="quick_settings_bluetooth_device_audio_sharing_or_switch_active">Supports audio sharing</string>
<!-- QuickSettings: Bluetooth dialog device saved default summary [CHAR LIMIT=NONE]-->
<string name="quick_settings_bluetooth_device_saved">Saved</string>
<!-- QuickSettings: Accessibility label to disconnect a device [CHAR LIMIT=NONE]-->
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
index b116e297ccbf..dcbacec5b630 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
@@ -16,6 +16,7 @@
package com.android.keyguard;
+import static com.android.systemui.Flags.simPinUseSlotId;
import static com.android.keyguard.logging.CarrierTextManagerLogger.REASON_ACTIVE_DATA_SUB_CHANGED;
import static com.android.keyguard.logging.CarrierTextManagerLogger.REASON_ON_TELEPHONY_CAPABLE;
import static com.android.keyguard.logging.CarrierTextManagerLogger.REASON_REFRESH_CARRIER_INFO;
@@ -368,10 +369,12 @@ public class CarrierTextManager {
for (int i = 0; i < numSubs; i++) {
int subId = subs.get(i).getSubscriptionId();
+ int slotId = subs.get(i).getSimSlotIndex();
carrierNames[i] = "";
subsIds[i] = subId;
- subOrderBySlot[subs.get(i).getSimSlotIndex()] = i;
- int simState = mKeyguardUpdateMonitor.getSimState(subId);
+ subOrderBySlot[slotId] = i;
+ int simState = simPinUseSlotId() ? mKeyguardUpdateMonitor.getSimStateForSlotId(slotId)
+ : mKeyguardUpdateMonitor.getSimState(subId);
CharSequence carrierName = subs.get(i).getCarrierName();
CharSequence carrierTextForSimState = getCarrierTextForSimState(simState, carrierName);
mLogger.logUpdateLoopStart(subId, simState, String.valueOf(carrierName));
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 8ca0e807b31c..2c8fff83a821 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -44,6 +44,7 @@ import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STR
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
import static com.android.systemui.Flags.simPinBouncerReset;
+import static com.android.systemui.Flags.simPinUseSlotId;
import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED;
import android.annotation.AnyThread;
@@ -172,7 +173,6 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
@@ -318,6 +318,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private final Object mSimDataLockObject = new Object();
HashMap<Integer, SimData> mSimDatas = new HashMap<>();
+ HashMap<Integer, SimData> mSimDatasBySlotId = new HashMap<>();
HashMap<Integer, ServiceState> mServiceStates = new HashMap<>();
private int mPhoneState;
@@ -616,16 +617,17 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
// It is possible for active subscriptions to become invalid (-1), and these will
// not be present in the subscriptionInfo list
synchronized (mSimDataLockObject) {
- Iterator<Map.Entry<Integer, SimData>> iter = mSimDatas.entrySet().iterator();
+ var iter = simPinUseSlotId() ? mSimDatasBySlotId.entrySet().iterator()
+ : mSimDatas.entrySet().iterator();
+
while (iter.hasNext()) {
- Map.Entry<Integer, SimData> simData = iter.next();
- if (!activeSubIds.contains(simData.getKey())) {
- mSimLogger.logInvalidSubId(simData.getKey());
+ SimData data = iter.next().getValue();
+ if (!activeSubIds.contains(data.subId)) {
+ mSimLogger.logInvalidSubId(data.subId, data.slotId);
iter.remove();
- SimData data = simData.getValue();
for (int j = 0; j < mCallbacks.size(); j++) {
- KeyguardUpdateMonitorCallback cb = mCallbacks.get(j).get();
+ var cb = mCallbacks.get(j).get();
if (cb != null) {
cb.onSimStateChanged(data.subId, data.slotId, data.simState);
}
@@ -634,10 +636,15 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
for (int i = 0; i < changedSubscriptions.size(); i++) {
- SimData data = mSimDatas.get(
- changedSubscriptions.get(i).getSubscriptionId());
+ SimData data;
+ if (simPinUseSlotId()) {
+ data = mSimDatasBySlotId.get(changedSubscriptions.get(i)
+ .getSimSlotIndex());
+ } else {
+ data = mSimDatas.get(changedSubscriptions.get(i).getSubscriptionId());
+ }
for (int j = 0; j < mCallbacks.size(); j++) {
- KeyguardUpdateMonitorCallback cb = mCallbacks.get(j).get();
+ var cb = mCallbacks.get(j).get();
if (cb != null) {
cb.onSimStateChanged(data.subId, data.slotId, data.simState);
}
@@ -3409,12 +3416,13 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
*/
private void invalidateSlot(int slotId) {
synchronized (mSimDataLockObject) {
- Iterator<Map.Entry<Integer, SimData>> iter = mSimDatas.entrySet().iterator();
+ var iter = simPinUseSlotId() ? mSimDatasBySlotId.entrySet().iterator()
+ : mSimDatas.entrySet().iterator();
while (iter.hasNext()) {
SimData data = iter.next().getValue();
if (data.slotId == slotId
&& SubscriptionManager.isValidSubscriptionId(data.subId)) {
- mSimLogger.logInvalidSubId(data.subId);
+ mSimLogger.logInvalidSubId(data.subId, data.slotId);
iter.remove();
}
}
@@ -3427,7 +3435,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
@VisibleForTesting
void handleSimStateChange(int subId, int slotId, int state) {
Assert.isMainThread();
- mSimLogger.logSimState(subId, slotId, state);
+ mSimLogger.logSimState(subId, slotId, TelephonyManager.simStateToString(state));
boolean becameAbsent = ABSENT_SIM_STATE_LIST.contains(state);
if (!SubscriptionManager.isValidSubscriptionId(subId)) {
@@ -3444,11 +3452,15 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
// TODO(b/327476182): Preserve SIM_STATE_CARD_IO_ERROR sims in a separate data source.
synchronized (mSimDataLockObject) {
- SimData data = mSimDatas.get(subId);
+ SimData data = simPinUseSlotId() ? mSimDatasBySlotId.get(slotId) : mSimDatas.get(subId);
final boolean changed;
if (data == null) {
data = new SimData(state, slotId, subId);
- mSimDatas.put(subId, data);
+ if (simPinUseSlotId()) {
+ mSimDatasBySlotId.put(slotId, data);
+ } else {
+ mSimDatas.put(subId, data);
+ }
changed = true; // no data yet; force update
} else {
changed = (data.simState != state || data.subId != subId || data.slotId != slotId);
@@ -3727,7 +3739,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
callback.onTelephonyCapable(mTelephonyCapable);
synchronized (mSimDataLockObject) {
- for (Entry<Integer, SimData> data : mSimDatas.entrySet()) {
+ var simDatas = simPinUseSlotId() ? mSimDatasBySlotId : mSimDatas;
+ for (Entry<Integer, SimData> data : simDatas.entrySet()) {
final SimData state = data.getValue();
callback.onSimStateChanged(state.subId, state.slotId, state.simState);
}
@@ -3860,7 +3873,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
*/
public boolean isSimPinSecure() {
synchronized (mSimDataLockObject) {
- for (SimData data : mSimDatas.values()) {
+ var simDatas = simPinUseSlotId() ? mSimDatasBySlotId : mSimDatas;
+ for (SimData data : simDatas.values()) {
if (isSimPinSecure(data.simState)) {
return true;
}
@@ -3870,6 +3884,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
public int getSimState(int subId) {
+ if (simPinUseSlotId()) {
+ throw new UnsupportedOperationException("Method not supported with flag "
+ + "simPinUseSlotId");
+ }
synchronized (mSimDataLockObject) {
if (mSimDatas.containsKey(subId)) {
return mSimDatas.get(subId).simState;
@@ -3879,12 +3897,32 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
}
+ /**
+ * Find the sim state for a slot id, or SIM_STATE_UNKNOWN if not found.
+ */
+ public int getSimStateForSlotId(int slotId) {
+ if (!simPinUseSlotId()) {
+ throw new UnsupportedOperationException("Method not supported without flag "
+ + "simPinUseSlotId");
+ }
+ synchronized (mSimDataLockObject) {
+ if (mSimDatasBySlotId.containsKey(slotId)) {
+ return mSimDatasBySlotId.get(slotId).simState;
+ } else {
+ return TelephonyManager.SIM_STATE_UNKNOWN;
+ }
+ }
+ }
+
private int getSlotId(int subId) {
synchronized (mSimDataLockObject) {
- if (!mSimDatas.containsKey(subId)) {
- refreshSimState(subId, SubscriptionManager.getSlotIndex(subId));
+ var simDatas = simPinUseSlotId() ? mSimDatasBySlotId : mSimDatas;
+ int slotId = SubscriptionManager.getSlotIndex(subId);
+ int index = simPinUseSlotId() ? slotId : subId;
+ if (!simDatas.containsKey(index)) {
+ refreshSimState(subId, slotId);
}
- SimData simData = mSimDatas.get(subId);
+ SimData simData = simDatas.get(index);
return simData != null ? simData.slotId : SubscriptionManager.INVALID_SUBSCRIPTION_ID;
}
}
@@ -3928,8 +3966,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private boolean refreshSimState(int subId, int slotId) {
int state = mTelephonyManager.getSimState(slotId);
synchronized (mSimDataLockObject) {
- SimData data = mSimDatas.get(subId);
-
+ SimData data = simPinUseSlotId() ? mSimDatasBySlotId.get(slotId) : mSimDatas.get(subId);
if (!SubscriptionManager.isValidSubscriptionId(subId)) {
invalidateSlot(slotId);
}
@@ -3937,7 +3974,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
final boolean changed;
if (data == null) {
data = new SimData(state, slotId, subId);
- mSimDatas.put(subId, data);
+ if (simPinUseSlotId()) {
+ mSimDatasBySlotId.put(slotId, data);
+ } else {
+ mSimDatas.put(subId, data);
+ }
changed = true; // no data yet; force update
} else {
changed = data.simState != state;
@@ -4033,10 +4074,17 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
for (int i = 0; i < list.size(); i++) {
final SubscriptionInfo info = list.get(i);
final int id = info.getSubscriptionId();
- int slotId = getSlotId(id);
- if (state == getSimState(id) && bestSlotId > slotId) {
- resultId = id;
- bestSlotId = slotId;
+ final int slotId = info.getSimSlotIndex();
+ if (simPinUseSlotId()) {
+ if (state == getSimStateForSlotId(slotId) && bestSlotId > slotId) {
+ resultId = id;
+ bestSlotId = slotId;
+ }
+ } else {
+ if (state == getSimState(id) && bestSlotId > slotId) {
+ resultId = id;
+ bestSlotId = slotId;
+ }
}
}
return resultId;
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/SimLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/SimLogger.kt
index a81698ba3387..fb26c6a00c27 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/SimLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/SimLogger.kt
@@ -47,12 +47,15 @@ class SimLogger @Inject constructor(@SimLog private val logBuffer: LogBuffer) {
fun log(@CompileTimeConstant msg: String, level: LogLevel) = logBuffer.log(TAG, level, msg)
- fun logInvalidSubId(subId: Int) {
+ fun logInvalidSubId(subId: Int, slotId: Int) {
logBuffer.log(
TAG,
INFO,
- { int1 = subId },
- { "Previously active sub id $int1 is now invalid, will remove" },
+ {
+ int1 = subId
+ int2 = slotId
+ },
+ { "Previously active subId: $int1, slotId: $int2 is now invalid, will remove" },
)
}
@@ -94,16 +97,16 @@ class SimLogger @Inject constructor(@SimLog private val logBuffer: LogBuffer) {
)
}
- fun logSimState(subId: Int, slotId: Int, state: Int) {
+ fun logSimState(subId: Int, slotId: Int, state: String) {
logBuffer.log(
TAG,
DEBUG,
{
int1 = subId
int2 = slotId
- long1 = state.toLong()
+ str1 = state
},
- { "handleSimStateChange(subId=$int1, slotId=$int2, state=$long1)" },
+ { "handleSimStateChange(subId=$int1, slotId=$int2, state=$str1)" },
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index 7d5cf232bcb9..3794e7bf6b55 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -283,7 +283,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
com.android.internal.R.integer.config_shortAnimTime));
updateDimensions();
- final Size windowFrameSize = restoreMagnificationWindowFrameSizeIfPossible();
+ final Size windowFrameSize = restoreMagnificationWindowFrameIndexAndSizeIfPossible();
setMagnificationFrame(windowFrameSize.getWidth(), windowFrameSize.getHeight(),
mWindowBounds.width() / 2, mWindowBounds.height() / 2);
computeBounceAnimationScale();
@@ -541,7 +541,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
return false;
}
mWindowBounds.set(currentWindowBounds);
- final Size windowFrameSize = restoreMagnificationWindowFrameSizeIfPossible();
+ final Size windowFrameSize = restoreMagnificationWindowFrameIndexAndSizeIfPossible();
final float newCenterX = (getCenterX()) * mWindowBounds.width() / oldWindowBounds.width();
final float newCenterY = (getCenterY()) * mWindowBounds.height() / oldWindowBounds.height();
@@ -787,18 +787,6 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
mMagnificationFrame.set(initX, initY, initX + width, initY + height);
}
- private Size restoreMagnificationWindowFrameSizeIfPossible() {
- if (Flags.saveAndRestoreMagnificationSettingsButtons()) {
- return restoreMagnificationWindowFrameIndexAndSizeIfPossible();
- }
-
- if (!mWindowMagnificationFrameSizePrefs.isPreferenceSavedForCurrentDensity()) {
- return getDefaultMagnificationWindowFrameSize();
- }
-
- return mWindowMagnificationFrameSizePrefs.getSizeForCurrentDensity();
- }
-
private Size restoreMagnificationWindowFrameIndexAndSizeIfPossible() {
if (!mWindowMagnificationFrameSizePrefs.isPreferenceSavedForCurrentDensity()) {
notifyWindowSizeRestored(MagnificationSize.DEFAULT);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefs.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefs.java
index ee36c6e8ad35..558c87c456ef 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefs.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefs.java
@@ -22,8 +22,6 @@ import android.content.Context;
import android.content.SharedPreferences;
import android.util.Size;
-import com.android.systemui.Flags;
-
/**
* Class to handle SharedPreference for window magnification size.
*/
@@ -52,14 +50,10 @@ final class WindowMagnificationFrameSizePrefs {
* Saves the window frame size for current screen density.
*/
public void saveIndexAndSizeForCurrentDensity(int index, Size size) {
- if (Flags.saveAndRestoreMagnificationSettingsButtons()) {
- mWindowMagnificationSizePreferences.edit()
- .putString(getKey(),
- WindowMagnificationFrameSpec.serialize(index, size)).apply();
- } else {
- mWindowMagnificationSizePreferences.edit()
- .putString(getKey(), size.toString()).apply();
- }
+ mWindowMagnificationSizePreferences
+ .edit()
+ .putString(getKey(), WindowMagnificationFrameSpec.serialize(index, size))
+ .apply();
}
/**
@@ -91,13 +85,9 @@ final class WindowMagnificationFrameSizePrefs {
* Gets the size preference for current screen density.
*/
public Size getSizeForCurrentDensity() {
- if (Flags.saveAndRestoreMagnificationSettingsButtons()) {
- return WindowMagnificationFrameSpec
- .deserialize(mWindowMagnificationSizePreferences.getString(getKey(), null))
- .getSize();
- } else {
- return Size.parseSize(mWindowMagnificationSizePreferences.getString(getKey(), null));
- }
+ return WindowMagnificationFrameSpec.deserialize(
+ mWindowMagnificationSizePreferences.getString(getKey(), null))
+ .getSize();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
index 2f0ca6e6bf9d..9525822ae141 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
@@ -59,7 +59,6 @@ import android.widget.Switch;
import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
-import com.android.systemui.Flags;
import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView;
import com.android.systemui.res.R;
import com.android.systemui.util.settings.SecureSettings;
@@ -460,12 +459,8 @@ class WindowMagnificationSettings implements MagnificationGestureDetector.OnGest
mAllowDiagonalScrollingView.setVisibility(View.VISIBLE);
mFullScreenButton.setVisibility(View.GONE);
if (selectedButtonIndex == MagnificationSize.FULLSCREEN) {
- if (Flags.saveAndRestoreMagnificationSettingsButtons()) {
- selectedButtonIndex =
- windowMagnificationFrameSizePrefs.getIndexForCurrentDensity();
- } else {
- selectedButtonIndex = MagnificationSize.CUSTOM;
- }
+ selectedButtonIndex =
+ windowMagnificationFrameSizePrefs.getIndexForCurrentDensity();
}
break;
@@ -482,10 +477,8 @@ class WindowMagnificationSettings implements MagnificationGestureDetector.OnGest
} else { // mode = ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW
mEditButton.setVisibility(View.VISIBLE);
mAllowDiagonalScrollingView.setVisibility(View.VISIBLE);
- if (Flags.saveAndRestoreMagnificationSettingsButtons()) {
- selectedButtonIndex =
- windowMagnificationFrameSizePrefs.getIndexForCurrentDensity();
- }
+ selectedButtonIndex =
+ windowMagnificationFrameSizePrefs.getIndexForCurrentDensity();
}
break;
diff --git a/packages/SystemUI/src/com/android/systemui/activity/ActivityManagerModule.kt b/packages/SystemUI/src/com/android/systemui/activity/ActivityManagerModule.kt
new file mode 100644
index 000000000000..db315e4e0bf7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/activity/ActivityManagerModule.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.activity
+
+import com.android.systemui.activity.data.repository.ActivityManagerRepository
+import com.android.systemui.activity.data.repository.ActivityManagerRepositoryImpl
+import com.android.systemui.dagger.SysUISingleton
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface ActivityManagerModule {
+ @Binds
+ @SysUISingleton
+ fun activityManagerRepository(impl: ActivityManagerRepositoryImpl): ActivityManagerRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/activity/data/repository/ActivityManagerRepository.kt b/packages/SystemUI/src/com/android/systemui/activity/data/repository/ActivityManagerRepository.kt
new file mode 100644
index 000000000000..94614b70beda
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/activity/data/repository/ActivityManagerRepository.kt
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.activity.data.repository
+
+import android.app.ActivityManager
+import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.log.core.Logger
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.onStart
+
+/** Repository for interfacing with [ActivityManager]. */
+interface ActivityManagerRepository {
+ /**
+ * Given a UID, creates a flow that emits true when the process with the given UID is visible to
+ * the user and false otherwise.
+ *
+ * @param identifyingLogTag a tag identifying who created this flow, used for logging.
+ */
+ fun createIsAppVisibleFlow(
+ creationUid: Int,
+ logger: Logger,
+ identifyingLogTag: String,
+ ): Flow<Boolean>
+}
+
+@SysUISingleton
+class ActivityManagerRepositoryImpl
+@Inject
+constructor(
+ @Background private val backgroundContext: CoroutineContext,
+ private val activityManager: ActivityManager,
+) : ActivityManagerRepository {
+ override fun createIsAppVisibleFlow(
+ creationUid: Int,
+ logger: Logger,
+ identifyingLogTag: String,
+ ): Flow<Boolean> {
+ return conflatedCallbackFlow {
+ val listener =
+ object : ActivityManager.OnUidImportanceListener {
+ override fun onUidImportance(uid: Int, importance: Int) {
+ if (uid != creationUid) {
+ return
+ }
+ val isAppVisible = isAppVisibleToUser(importance)
+ logger.d({
+ "$str1: #onUidImportance. importance=$int1, isAppVisible=$bool1"
+ }) {
+ str1 = identifyingLogTag
+ int1 = importance
+ bool1 = isAppVisible
+ }
+ trySend(isAppVisible)
+ }
+ }
+ try {
+ // TODO(b/286258140): Replace this with the #addOnUidImportanceListener
+ // overload that filters to certain UIDs.
+ activityManager.addOnUidImportanceListener(listener, IMPORTANCE_CUTPOINT)
+ } catch (e: SecurityException) {
+ logger.e({ "$str1: Security exception on #addOnUidImportanceListener" }, e) {
+ str1 = identifyingLogTag
+ }
+ }
+
+ awaitClose { activityManager.removeOnUidImportanceListener(listener) }
+ }
+ .distinctUntilChanged()
+ .onStart {
+ try {
+ val isVisibleOnStart =
+ isAppVisibleToUser(activityManager.getUidImportance(creationUid))
+ logger.d({ "$str1: Starting UID observation. isAppVisible=$bool1" }) {
+ str1 = identifyingLogTag
+ bool1 = isVisibleOnStart
+ }
+ emit(isVisibleOnStart)
+ } catch (e: SecurityException) {
+ logger.e({ "$str1: Security exception on #getUidImportance" }, e) {
+ str1 = identifyingLogTag
+ }
+ emit(false)
+ }
+ }
+ .flowOn(backgroundContext)
+ }
+
+ /** Returns true if the given [importance] represents an app that's visible to the user. */
+ private fun isAppVisibleToUser(importance: Int): Boolean {
+ return importance <= IMPORTANCE_CUTPOINT
+ }
+
+ companion object {
+ private const val IMPORTANCE_CUTPOINT = IMPORTANCE_FOREGROUND
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorImpl.kt
index 13c72097c06e..4dc2a13480f5 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorImpl.kt
@@ -27,7 +27,6 @@ import com.android.settingslib.bluetooth.HearingAidProfile
import com.android.settingslib.bluetooth.LeAudioProfile
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast
import com.android.settingslib.bluetooth.LocalBluetoothManager
-import com.android.settingslib.flags.Flags.audioSharingQsDialogImprovement
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
@@ -69,7 +68,7 @@ constructor(
}
deviceItem.type ==
DeviceItemType.AVAILABLE_AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE -> {
- if (audioSharingQsDialogImprovement()) {
+ if (audioSharingInteractor.qsDialogImprovementAvailable()) {
withContext(mainDispatcher) {
delegateFactory
.create(deviceItem.cachedBluetoothDevice)
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractor.kt
index 65f110533573..c4f26cd46bf8 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractor.kt
@@ -16,10 +16,13 @@
package com.android.systemui.bluetooth.qsdialog
+import android.content.Context
+import androidx.annotation.WorkerThread
import com.android.settingslib.bluetooth.BluetoothUtils
import com.android.settingslib.bluetooth.CachedBluetoothDevice
import com.android.settingslib.bluetooth.LocalBluetoothManager
-import com.android.settingslib.bluetooth.onPlaybackStarted
+import com.android.settingslib.bluetooth.onBroadcastMetadataChanged
+import com.android.settingslib.flags.Flags.audioSharingQsDialogImprovement
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import javax.inject.Inject
@@ -52,6 +55,8 @@ interface AudioSharingInteractor {
suspend fun startAudioSharing()
suspend fun audioSharingAvailable(): Boolean
+
+ suspend fun qsDialogImprovementAvailable(): Boolean
}
@SysUISingleton
@@ -59,11 +64,14 @@ interface AudioSharingInteractor {
class AudioSharingInteractorImpl
@Inject
constructor(
+ private val context: Context,
private val localBluetoothManager: LocalBluetoothManager?,
private val audioSharingRepository: AudioSharingRepository,
@Background private val backgroundDispatcher: CoroutineDispatcher,
) : AudioSharingInteractor {
+ private var previewEnabled: Boolean? = null
+
override val isAudioSharingOn: Flow<Boolean> =
flow { emit(audioSharingAvailable()) }
.flatMapLatest { isEnabled ->
@@ -93,10 +101,10 @@ constructor(
isAudioSharingOn
.mapNotNull { audioSharingOn ->
if (audioSharingOn) {
- // onPlaybackStarted could emit multiple times during one
- // audio sharing session, we only perform add source on the
- // first time
- profile.onPlaybackStarted.firstOrNull()
+ // onBroadcastMetadataChanged could emit multiple times during one
+ // audio sharing session, we only perform add source on the first
+ // time
+ profile.onBroadcastMetadataChanged.firstOrNull()
} else {
null
}
@@ -141,6 +149,20 @@ constructor(
override suspend fun audioSharingAvailable(): Boolean {
return audioSharingRepository.audioSharingAvailable()
}
+
+ override suspend fun qsDialogImprovementAvailable(): Boolean {
+ return withContext(backgroundDispatcher) {
+ audioSharingQsDialogImprovement() || isAudioSharingPreviewEnabled()
+ }
+ }
+
+ @WorkerThread
+ private fun isAudioSharingPreviewEnabled(): Boolean {
+ if (previewEnabled == null) {
+ previewEnabled = BluetoothUtils.isAudioSharingPreviewEnabled(context.contentResolver)
+ }
+ return previewEnabled ?: false
+ }
}
@SysUISingleton
@@ -160,4 +182,6 @@ class AudioSharingInteractorEmptyImpl @Inject constructor() : AudioSharingIntera
override suspend fun startAudioSharing() {}
override suspend fun audioSharingAvailable(): Boolean = false
+
+ override suspend fun qsDialogImprovementAvailable(): Boolean = false
}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
index b255a4f55220..497d8cf2e159 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
@@ -26,10 +26,10 @@ import android.view.ViewGroup
import androidx.annotation.DimenRes
import androidx.annotation.StringRes
import androidx.annotation.VisibleForTesting
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.logging.UiEventLogger
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast
-import com.android.settingslib.flags.Flags.audioSharingQsDialogImprovement
import com.android.systemui.Prefs
import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogTransitionAnimator
@@ -56,7 +56,6 @@ import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onEach
-import com.android.app.tracing.coroutines.launchTraced as launch
import kotlinx.coroutines.withContext
/** ViewModel for Bluetooth Dialog after clicking on the Bluetooth QS tile. */
@@ -145,7 +144,7 @@ constructor(
bluetoothDeviceMetadataInteractor.metadataUpdate,
if (
audioSharingInteractor.audioSharingAvailable() &&
- audioSharingQsDialogImprovement()
+ audioSharingInteractor.qsDialogImprovementAvailable()
) {
audioSharingInteractor.audioSourceStateUpdate
} else {
@@ -165,7 +164,7 @@ constructor(
.launchIn(this)
if (audioSharingInteractor.audioSharingAvailable()) {
- if (audioSharingQsDialogImprovement()) {
+ if (audioSharingInteractor.qsDialogImprovementAvailable()) {
launch { audioSharingInteractor.handleAudioSourceWhenReady() }
}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/dagger/AudioSharingModule.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/dagger/AudioSharingModule.kt
index 8b5fde384837..afe9a1eec0b6 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/dagger/AudioSharingModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/dagger/AudioSharingModule.kt
@@ -55,7 +55,10 @@ interface AudioSharingModule {
settingsLibAudioSharingRepository: SettingsLibAudioSharingRepository,
@Background backgroundDispatcher: CoroutineDispatcher,
): AudioSharingRepository =
- if (Flags.enableLeAudioSharing() && localBluetoothManager != null) {
+ if (
+ (Flags.enableLeAudioSharing() || Flags.audioSharingDeveloperOption()) &&
+ localBluetoothManager != null
+ ) {
AudioSharingRepositoryImpl(
localBluetoothManager,
settingsLibAudioSharingRepository,
@@ -72,7 +75,10 @@ interface AudioSharingModule {
impl: Lazy<AudioSharingInteractorImpl>,
emptyImpl: Lazy<AudioSharingInteractorEmptyImpl>,
): AudioSharingInteractor =
- if (Flags.enableLeAudioSharing() && localBluetoothManager != null) {
+ if (
+ (Flags.enableLeAudioSharing() || Flags.audioSharingDeveloperOption()) &&
+ localBluetoothManager != null
+ ) {
impl.get()
} else {
emptyImpl.get()
@@ -85,7 +91,10 @@ interface AudioSharingModule {
audioSharingImpl: Lazy<AudioSharingDeviceItemActionInteractorImpl>,
impl: Lazy<DeviceItemActionInteractorImpl>,
): DeviceItemActionInteractor =
- if (Flags.enableLeAudioSharing() && localBluetoothManager != null) {
+ if (
+ (Flags.enableLeAudioSharing() || Flags.audioSharingDeveloperOption()) &&
+ localBluetoothManager != null
+ ) {
audioSharingImpl.get()
} else {
impl.get()
@@ -97,7 +106,10 @@ interface AudioSharingModule {
localBluetoothManager: LocalBluetoothManager?
): List<DeviceItemFactory> = buildList {
add(ActiveMediaDeviceItemFactory())
- if (Flags.enableLeAudioSharing() && localBluetoothManager != null) {
+ if (
+ (Flags.enableLeAudioSharing() || Flags.audioSharingDeveloperOption()) &&
+ localBluetoothManager != null
+ ) {
add(AudioSharingMediaDeviceItemFactory(localBluetoothManager))
add(AvailableAudioSharingMediaDeviceItemFactory(localBluetoothManager))
}
@@ -112,7 +124,10 @@ interface AudioSharingModule {
localBluetoothManager: LocalBluetoothManager?
): List<DeviceItemType> = buildList {
add(DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE)
- if (Flags.enableLeAudioSharing() && localBluetoothManager != null) {
+ if (
+ (Flags.enableLeAudioSharing() || Flags.audioSharingDeveloperOption()) &&
+ localBluetoothManager != null
+ ) {
add(DeviceItemType.AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE)
add(DeviceItemType.AVAILABLE_AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE)
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index 5ecf2e6b2551..a339af3694e7 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -195,7 +195,7 @@ abstract class BaseCommunalViewModel(
open fun onDismissCtaTile() {}
/** Called as the user starts dragging a widget to reorder. */
- open fun onReorderWidgetStart() {}
+ open fun onReorderWidgetStart(draggingItemKey: String) {}
/** Called as the user finishes dragging a widget to reorder. */
open fun onReorderWidgetEnd() {}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index 736ed5c7d336..52bf0004cbe4 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -164,9 +164,8 @@ constructor(
)
}
- override fun onReorderWidgetStart() {
- // Clear selection status
- setSelectedKey(null)
+ override fun onReorderWidgetStart(draggingItemKey: String) {
+ setSelectedKey(draggingItemKey)
_reorderingWidgets.value = true
uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_START)
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index 2e323d40edcd..1fc549469b55 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -75,7 +75,7 @@ import com.android.systemui.statusbar.dagger.StartCentralSurfacesModule;
import com.android.systemui.statusbar.notification.dagger.ReferenceNotificationsModule;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.DozeServiceHost;
-import com.android.systemui.statusbar.phone.HeadsUpModule;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpModule;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.phone.dagger.StatusBarPhoneModule;
import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragmentStartableModule;
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 38cfb9b333a8..fcc3ea9f7d58 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -54,7 +54,7 @@ import com.android.systemui.shortcut.ShortcutKeyDispatcher
import com.android.systemui.statusbar.ImmersiveModeConfirmation
import com.android.systemui.statusbar.gesture.GesturePointerEventListener
import com.android.systemui.statusbar.notification.InstantAppNotifier
-import com.android.systemui.statusbar.phone.StatusBarHeadsUpChangeListener
+import com.android.systemui.statusbar.notification.headsup.StatusBarHeadsUpChangeListener
import com.android.systemui.statusbar.policy.BatteryControllerStartable
import com.android.systemui.stylus.StylusUsiPowerStartable
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index b7d3c9253f51..d6f8957ace33 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -32,6 +32,7 @@ import com.android.systemui.BootCompleteCacheImpl;
import com.android.systemui.CameraProtectionModule;
import com.android.systemui.CoreStartable;
import com.android.systemui.SystemUISecondaryUserService;
+import com.android.systemui.activity.ActivityManagerModule;
import com.android.systemui.ambient.dagger.AmbientModule;
import com.android.systemui.appops.dagger.AppOpsModule;
import com.android.systemui.assist.AssistModule;
@@ -140,7 +141,7 @@ import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.ConfigurationControllerModule;
import com.android.systemui.statusbar.phone.LetterboxModule;
import com.android.systemui.statusbar.pipeline.dagger.StatusBarPipelineModule;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.PolicyModule;
import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController;
@@ -198,6 +199,7 @@ import javax.inject.Named;
* may not appreciate that.
*/
@Module(includes = {
+ ActivityManagerModule.class,
AmbientModule.class,
AppOpsModule.class,
AssistModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModel.kt
index b37206a4fef7..160574fa2244 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModel.kt
@@ -19,6 +19,7 @@ package com.android.systemui.dreams.ui.viewmodel
import com.android.compose.animation.scene.Swipe
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.viewmodel.UserActionsViewModel
@@ -38,6 +39,7 @@ import kotlinx.coroutines.flow.map
class DreamUserActionsViewModel
@AssistedInject
constructor(
+ private val communalInteractor: CommunalInteractor,
private val deviceUnlockedInteractor: DeviceUnlockedInteractor,
private val shadeInteractor: ShadeInteractor,
) : UserActionsViewModel() {
@@ -50,10 +52,13 @@ constructor(
} else {
combine(
deviceUnlockedInteractor.deviceUnlockStatus.map { it.isUnlocked },
+ communalInteractor.isCommunalAvailable,
shadeInteractor.shadeMode,
- ) { isDeviceUnlocked, shadeMode ->
+ ) { isDeviceUnlocked, isCommunalAvailable, shadeMode ->
buildList {
- add(Swipe.Start to Scenes.Communal)
+ if (isCommunalAvailable) {
+ add(Swipe.Start to Scenes.Communal)
+ }
val bouncerOrGone =
if (isDeviceUnlocked) Scenes.Gone else Scenes.Bouncer
diff --git a/packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduUiCoordinator.kt b/packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduUiCoordinator.kt
index 2a3729b1aeab..c49ba80c660b 100644
--- a/packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduUiCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduUiCoordinator.kt
@@ -27,6 +27,7 @@ import android.os.Bundle
import android.os.UserHandle
import android.view.accessibility.AccessibilityManager
import androidx.core.app.NotificationCompat
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -39,7 +40,6 @@ import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutoria
import com.android.systemui.res.R
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
-import com.android.app.tracing.coroutines.launchTraced as launch
/**
* A class to show contextual education on UI based on the edu produced from
@@ -107,6 +107,7 @@ constructor(
}
private fun showDialog(model: ContextualEduToastViewModel) {
+ dialog?.dismiss()
dialog = createDialog(model)
dialog?.show()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt
index 99cafd3daacb..321fd57d3e8b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomShortcutCategoriesRepository.kt
@@ -28,6 +28,7 @@ import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_RES
import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_SUCCESS
import android.hardware.input.InputSettings
import android.hardware.input.KeyGestureEvent
+import android.hardware.input.KeyGestureEvent.KeyGestureType
import android.util.Log
import androidx.annotation.VisibleForTesting
import androidx.compose.runtime.mutableStateOf
@@ -44,6 +45,8 @@ import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomization
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutHelperState.Active
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
import com.android.systemui.settings.UserTracker
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -52,8 +55,6 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext
-import javax.inject.Inject
-import kotlin.coroutines.CoroutineContext
@SysUISingleton
class CustomShortcutCategoriesRepository
@@ -65,7 +66,7 @@ constructor(
@Background private val bgCoroutineContext: CoroutineContext,
private val shortcutCategoriesUtils: ShortcutCategoriesUtils,
private val context: Context,
- private val inputGestureMaps: InputGestureMaps
+ private val inputGestureMaps: InputGestureMaps,
) : ShortcutCategoriesRepository {
private val userContext: Context
@@ -130,9 +131,7 @@ constructor(
emptyList()
} else {
val customInputGesturesForUser: List<InputGestureData> =
- if (InputSettings.isCustomizableInputGesturesFeatureFlagEnabled()) {
- inputManager.getCustomInputGestures(/* filter= */ null)
- } else emptyList()
+ getCustomInputGestures()
val sources = toInternalGroupSources(customInputGesturesForUser)
val supportedKeyCodes =
shortcutCategoriesUtils.fetchSupportedKeyCodes(
@@ -173,16 +172,20 @@ constructor(
.addKeyGestureTypeFromShortcutLabel()
.addTriggerFromSelectedKeyCombination()
.build()
- // TODO(b/379648200) add app launch data for application categories shortcut after
- // dynamic
- // label/icon mapping implementation
+ // TODO(b/379648200) add app launch data after dynamic label/icon mapping implementation
} catch (e: IllegalArgumentException) {
Log.w(TAG, "could not add custom shortcut: $e")
return null
}
}
- suspend fun confirmAndSetShortcutCurrentlyBeingCustomized(): ShortcutCustomizationRequestResult {
+ private fun retrieveInputGestureDataForShortcutBeingDeleted(): InputGestureData? {
+ val keyGestureType = getKeyGestureTypeFromShortcutBeingDeletedLabel()
+ return getCustomInputGestures().firstOrNull { it.action.keyGestureType() == keyGestureType }
+ }
+
+ suspend fun confirmAndSetShortcutCurrentlyBeingCustomized():
+ ShortcutCustomizationRequestResult {
return withContext(bgCoroutineContext) {
val inputGestureData =
buildInputGestureDataForShortcutBeingCustomized()
@@ -201,24 +204,79 @@ constructor(
}
}
+ suspend fun deleteShortcutCurrentlyBeingCustomized():
+ ShortcutCustomizationRequestResult {
+ return withContext(bgCoroutineContext) {
+ val inputGestureData =
+ retrieveInputGestureDataForShortcutBeingDeleted()
+ ?: return@withContext ShortcutCustomizationRequestResult.ERROR_OTHER
+ return@withContext when (
+ val result = inputManager.removeCustomInputGesture(inputGestureData)
+ ) {
+ CUSTOM_INPUT_GESTURE_RESULT_SUCCESS -> ShortcutCustomizationRequestResult.SUCCESS
+ else -> {
+ Log.w(
+ TAG,
+ "Attempted to delete shortcut being customized " +
+ "${_shortcutBeingCustomized.value} but ran into an error. InputGestureData" +
+ " = $inputGestureData, error code: $result",
+ )
+ ShortcutCustomizationRequestResult.ERROR_OTHER
+ }
+ }
+ }
+ }
+
+ private fun getCustomInputGestures(): List<InputGestureData> {
+ return if (InputSettings.isCustomizableInputGesturesFeatureFlagEnabled()) {
+ inputManager.getCustomInputGestures(/* filter= */ InputGestureData.Filter.KEY)
+ } else emptyList()
+ }
+
private fun Builder.addKeyGestureTypeFromShortcutLabel(): Builder {
+ val keyGestureType = getKeyGestureTypeFromShortcutBeingCustomizedLabel()
+
+ if (keyGestureType == null) {
+ Log.w(
+ TAG,
+ "Could not find KeyGestureType for shortcut ${_shortcutBeingCustomized.value}",
+ )
+ return this
+ }
+
+ return setKeyGestureType(keyGestureType)
+ }
+
+ @KeyGestureType
+ private fun getKeyGestureTypeFromShortcutBeingCustomizedLabel(): Int? {
val shortcutBeingCustomized =
getShortcutBeingCustomized() as? ShortcutCustomizationRequestInfo.Add
if (shortcutBeingCustomized == null) {
- Log.w(TAG, "User requested to set shortcut but shortcut being customized is null")
- return this
+ Log.w(
+ TAG,
+ "Requested key gesture type from label but shortcut being customized is null",
+ )
+ return null
}
- val keyGestureType =
- inputGestureMaps.shortcutLabelToKeyGestureTypeMap[shortcutBeingCustomized.label]
+ return inputGestureMaps.shortcutLabelToKeyGestureTypeMap[shortcutBeingCustomized.label]
+ }
- if (keyGestureType == null) {
- Log.w(TAG, "Could not find KeyGestureType for shortcut $shortcutBeingCustomized")
- return this
+ @KeyGestureType
+ private fun getKeyGestureTypeFromShortcutBeingDeletedLabel(): Int? {
+ val shortcutBeingCustomized =
+ getShortcutBeingCustomized() as? ShortcutCustomizationRequestInfo.Delete
+
+ if (shortcutBeingCustomized == null) {
+ Log.w(
+ TAG,
+ "Requested key gesture type from label but shortcut being customized is null",
+ )
+ return null
}
- return setKeyGestureType(keyGestureType)
+ return inputGestureMaps.shortcutLabelToKeyGestureTypeMap[shortcutBeingCustomized.label]
}
private fun Builder.addTriggerFromSelectedKeyCombination(): Builder {
@@ -227,7 +285,7 @@ constructor(
Log.w(
TAG,
"User requested to set shortcut but selected key combination is " +
- "$selectedKeyCombination",
+ "$selectedKeyCombination",
)
return this
}
@@ -235,8 +293,7 @@ constructor(
return setTrigger(
createKeyTrigger(
/* keycode = */ selectedKeyCombination.keyCode,
- /* modifierState = */
- shortcutCategoriesUtils.removeUnsupportedModifiers(
+ /* modifierState = */ shortcutCategoriesUtils.removeUnsupportedModifiers(
selectedKeyCombination.modifiers
),
)
@@ -256,10 +313,8 @@ constructor(
val keyTrigger = gestureData.trigger as KeyTrigger
val keyGestureType = gestureData.action.keyGestureType()
fetchGroupLabelByGestureType(keyGestureType)?.let { groupLabel ->
- toInternalKeyboardShortcutInfo(
- keyGestureType,
- keyTrigger
- )?.let { internalKeyboardShortcutInfo ->
+ toInternalKeyboardShortcutInfo(keyGestureType, keyTrigger)?.let {
+ internalKeyboardShortcutInfo ->
val group =
InternalKeyboardShortcutGroup(
label = groupLabel,
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt
index d228a15e51b4..ecc076178d2d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt
@@ -48,9 +48,7 @@ import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.
import com.android.systemui.res.R
import javax.inject.Inject
-class InputGestureMaps
-@Inject
-constructor(private val context: Context) {
+class InputGestureMaps @Inject constructor(private val context: Context) {
val gestureToShortcutCategoryTypeMap =
mapOf(
// System Category
@@ -180,9 +178,10 @@ constructor(private val context: Context) {
)
val shortcutLabelToKeyGestureTypeMap: Map<String, Int>
- get() = gestureToInternalKeyboardShortcutInfoLabelResIdMap.entries.associateBy({
- context.getString(it.value)
- }) {
- it.key
- }
+ get() =
+ gestureToInternalKeyboardShortcutInfoLabelResIdMap.entries.associateBy({
+ context.getString(it.value)
+ }) {
+ it.key
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutCategoriesUtils.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutCategoriesUtils.kt
index 95bc9f66618c..a0897f293624 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutCategoriesUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutCategoriesUtils.kt
@@ -36,9 +36,9 @@ import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCommand
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutIcon
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
-import kotlinx.coroutines.withContext
import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.withContext
class ShortcutCategoriesUtils
@Inject
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutCustomizationInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutCustomizationInteractor.kt
index f4e2f05379bb..7743c53c6900 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutCustomizationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutCustomizationInteractor.kt
@@ -41,7 +41,13 @@ constructor(private val customShortcutRepository: CustomShortcutCategoriesReposi
customShortcutRepository.onCustomizationRequested(requestInfo)
}
- suspend fun confirmAndSetShortcutCurrentlyBeingCustomized(): ShortcutCustomizationRequestResult {
+ suspend fun confirmAndSetShortcutCurrentlyBeingCustomized():
+ ShortcutCustomizationRequestResult {
return customShortcutRepository.confirmAndSetShortcutCurrentlyBeingCustomized()
}
+
+ suspend fun deleteShortcutCurrentlyBeingCustomized():
+ ShortcutCustomizationRequestResult {
+ return customShortcutRepository.deleteShortcutCurrentlyBeingCustomized()
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCategory.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCategory.kt
index 813a1fcac65d..464805334c2a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCategory.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCategory.kt
@@ -18,25 +18,31 @@ package com.android.systemui.keyboard.shortcut.shared.model
sealed interface ShortcutCategoryType {
val isTrusted: Boolean
+ val includeInCustomization: Boolean
data object System : ShortcutCategoryType {
override val isTrusted: Boolean = true
+ override val includeInCustomization: Boolean = true
}
data object MultiTasking : ShortcutCategoryType {
override val isTrusted: Boolean = true
+ override val includeInCustomization: Boolean = true
}
data object InputMethodEditor : ShortcutCategoryType {
override val isTrusted: Boolean = false
+ override val includeInCustomization: Boolean = false
}
data object AppCategories : ShortcutCategoryType {
override val isTrusted: Boolean = true
+ override val includeInCustomization: Boolean = true
}
data class CurrentApp(val packageName: String) : ShortcutCategoryType {
override val isTrusted: Boolean = false
+ override val includeInCustomization: Boolean = false
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt
index 67caadba0d45..f28618bb8cf4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt
@@ -73,22 +73,17 @@ constructor(
return dialogFactory.create(dialogDelegate = ShortcutCustomizationDialogDelegate()) { dialog
->
val uiState by
- viewModel.shortcutCustomizationUiState.collectAsStateWithLifecycle(
- initialValue = ShortcutCustomizationUiState.Inactive
- )
+ viewModel.shortcutCustomizationUiState.collectAsStateWithLifecycle(
+ initialValue = ShortcutCustomizationUiState.Inactive
+ )
val coroutineScope = rememberCoroutineScope()
ShortcutCustomizationDialog(
uiState = uiState,
- modifier = Modifier
- .width(364.dp)
- .wrapContentHeight()
- .padding(vertical = 24.dp),
+ modifier = Modifier.width(364.dp).wrapContentHeight().padding(vertical = 24.dp),
onKeyPress = { viewModel.onKeyPressed(it) },
onCancel = { dialog.dismiss() },
- onConfirmSetShortcut = {
- coroutineScope.launch { viewModel.onSetShortcut() }
- },
- onConfirmDeleteShortcut = { viewModel.onDeleteShortcut() },
+ onConfirmSetShortcut = { coroutineScope.launch { viewModel.onSetShortcut() } },
+ onConfirmDeleteShortcut = { coroutineScope.launch { viewModel.deleteShortcutCurrentlyBeingCustomized() } },
)
dialog.setOnDismissListener { viewModel.onDialogDismissed() }
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
index f41ff7c69684..e3675de5d197 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
@@ -461,7 +461,7 @@ private fun EndSidePanel(
SubCategoryContainerDualPane(
searchQuery = searchQuery,
subCategory = subcategory,
- isCustomizing = isCustomizing,
+ isCustomizing = isCustomizing and category.type.includeInCustomization,
onCustomizationRequested = { requestInfo ->
when (requestInfo) {
is ShortcutCustomizationRequestInfo.Add ->
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt
index 869682742043..b467bb481443 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt
@@ -79,8 +79,7 @@ constructor(
}
is ShortcutCustomizationRequestInfo.Delete -> {
- _shortcutCustomizationUiState.value =
- DeleteShortcutDialog(isDialogShowing = false)
+ _shortcutCustomizationUiState.value = DeleteShortcutDialog(isDialogShowing = false)
shortcutCustomizationInteractor.onCustomizationRequested(requestInfo)
}
}
@@ -134,17 +133,23 @@ constructor(
}
}
- fun onDeleteShortcut() {
- // TODO(b/373631984) not yet implemented
+ suspend fun deleteShortcutCurrentlyBeingCustomized() {
+ val result =
+ shortcutCustomizationInteractor.deleteShortcutCurrentlyBeingCustomized()
+
+ _shortcutCustomizationUiState.update { uiState ->
+ when (result) {
+ ShortcutCustomizationRequestResult.SUCCESS -> ShortcutCustomizationUiState.Inactive
+ else -> uiState
+ }
+ }
}
private fun getUiStateWithErrorMessage(
uiState: ShortcutCustomizationUiState,
errorMessage: String,
): ShortcutCustomizationUiState {
- return (uiState as? AddShortcutDialog)?.copy(
- errorMessage = errorMessage
- ) ?: uiState
+ return (uiState as? AddShortcutDialog)?.copy(errorMessage = errorMessage) ?: uiState
}
private fun updatePressedKeys(keyEvent: KeyEvent) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 7097c1d2fc4e..d40fe468b0a5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -81,7 +81,7 @@ import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.domain.interactor.KeyguardDismissInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardEnabledInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
-import com.android.systemui.keyguard.domain.interactor.KeyguardLockWhileAwakeInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardServiceLockNowInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardStateCallbackInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardWakeDirectlyToGoneInteractor;
import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindParamsApplier;
@@ -330,8 +330,7 @@ public class KeyguardService extends Service {
return new FoldGracePeriodProvider();
}
};
- private final KeyguardLockWhileAwakeInteractor
- mKeyguardLockWhileAwakeInteractor;
+ private final KeyguardServiceLockNowInteractor mKeyguardServiceLockNowInteractor;
@Inject
public KeyguardService(
@@ -357,7 +356,7 @@ public class KeyguardService extends Service {
KeyguardDismissInteractor keyguardDismissInteractor,
Lazy<DeviceEntryInteractor> deviceEntryInteractorLazy,
KeyguardStateCallbackInteractor keyguardStateCallbackInteractor,
- KeyguardLockWhileAwakeInteractor keyguardLockWhileAwakeInteractor) {
+ KeyguardServiceLockNowInteractor keyguardServiceLockNowInteractor) {
super();
mKeyguardViewMediator = keyguardViewMediator;
mKeyguardLifecyclesDispatcher = keyguardLifecyclesDispatcher;
@@ -389,7 +388,7 @@ public class KeyguardService extends Service {
mKeyguardEnabledInteractor = keyguardEnabledInteractor;
mKeyguardWakeDirectlyToGoneInteractor = keyguardWakeDirectlyToGoneInteractor;
mKeyguardDismissInteractor = keyguardDismissInteractor;
- mKeyguardLockWhileAwakeInteractor = keyguardLockWhileAwakeInteractor;
+ mKeyguardServiceLockNowInteractor = keyguardServiceLockNowInteractor;
}
@Override
@@ -665,7 +664,7 @@ public class KeyguardService extends Service {
if (SceneContainerFlag.isEnabled()) {
mDeviceEntryInteractorLazy.get().lockNow();
} else if (KeyguardWmStateRefactor.isEnabled()) {
- mKeyguardLockWhileAwakeInteractor.onKeyguardServiceDoKeyguardTimeout(options);
+ mKeyguardServiceLockNowInteractor.onKeyguardServiceDoKeyguardTimeout(options);
}
mKeyguardViewMediator.doKeyguardTimeout(options);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 01ec4d026646..9f131607cb99 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -3943,7 +3943,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
}
private void notifyDefaultDisplayCallbacks(boolean showing) {
- if (SceneContainerFlag.isEnabled()) {
+ if (SceneContainerFlag.isEnabled() || KeyguardWmStateRefactor.isEnabled()) {
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt
index 631e44aca26d..42cbd7d39248 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt
@@ -16,39 +16,52 @@
package com.android.systemui.keyguard.domain.interactor
+import com.android.app.tracing.coroutines.launchTraced as launch
+import com.android.internal.widget.LockPatternUtils
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
-import com.android.app.tracing.coroutines.launchTraced as launch
+import kotlinx.coroutines.withContext
/**
- * Logic around the keyguard being enabled/disabled, per [KeyguardService]. If the keyguard is not
- * enabled, the lockscreen cannot be shown and the device will go from AOD/DOZING directly to GONE.
+ * Logic around the keyguard being enabled, disabled, or suppressed via adb. If the keyguard is
+ * disabled or suppressed, the lockscreen cannot be shown and the device will go from AOD/DOZING
+ * directly to GONE.
*
* Keyguard can be disabled by selecting Security: "None" in settings, or by apps that hold
* permission to do so (such as Phone). Some CTS tests also disable keyguard in onCreate or onStart
* rather than simply dismissing the keyguard or setting up the device to have Security: None, for
* reasons unknown.
+ *
+ * Keyguard can be suppressed by calling "adb shell locksettings set-disabled true", which is
+ * frequently done in tests. If keyguard is suppressed, it won't show even if the keyguard is
+ * enabled. If keyguard is not suppressed, then we defer to whether keyguard is enabled or disabled.
*/
@SysUISingleton
class KeyguardEnabledInteractor
@Inject
constructor(
- @Application scope: CoroutineScope,
+ @Application val scope: CoroutineScope,
+ @Background val backgroundDispatcher: CoroutineDispatcher,
val repository: KeyguardRepository,
val biometricSettingsRepository: BiometricSettingsRepository,
- keyguardDismissTransitionInteractor: KeyguardDismissTransitionInteractor,
+ private val selectedUserInteractor: SelectedUserInteractor,
+ private val lockPatternUtils: LockPatternUtils,
+ keyguardDismissTransitionInteractor: dagger.Lazy<KeyguardDismissTransitionInteractor>,
internalTransitionInteractor: InternalKeyguardTransitionInteractor,
) {
@@ -62,6 +75,10 @@ constructor(
* If the keyguard is disabled while we're locked, we will transition to GONE unless we're in
* lockdown mode. If the keyguard is re-enabled, we'll transition back to LOCKSCREEN if we were
* locked when it was disabled.
+ *
+ * Even if the keyguard is enabled, it's possible for it to be suppressed temporarily via adb.
+ * If you need to respect that adb command, you will need to use
+ * [isKeyguardEnabledAndNotSuppressed] instead of using this flow.
*/
val isKeyguardEnabled: StateFlow<Boolean> = repository.isKeyguardEnabled
@@ -96,9 +113,9 @@ constructor(
val currentTransitionInfo =
internalTransitionInteractor.currentTransitionInfoInternal()
if (currentTransitionInfo.to != KeyguardState.GONE && !inLockdown) {
- keyguardDismissTransitionInteractor.startDismissKeyguardTransition(
- "keyguard disabled"
- )
+ keyguardDismissTransitionInteractor
+ .get()
+ .startDismissKeyguardTransition("keyguard disabled")
}
}
}
@@ -116,4 +133,37 @@ constructor(
fun isShowKeyguardWhenReenabled(): Boolean {
return repository.isShowKeyguardWhenReenabled()
}
+
+ /**
+ * Whether the keyguard is enabled, and has not been suppressed via adb.
+ *
+ * There is unfortunately no callback for [isKeyguardSuppressed], which means this can't be a
+ * flow, since it's ambiguous when we would query the latest suppression value.
+ */
+ suspend fun isKeyguardEnabledAndNotSuppressed(): Boolean {
+ return isKeyguardEnabled.value && !isKeyguardSuppressed()
+ }
+
+ /**
+ * Returns whether the lockscreen has been disabled ("suppressed") via "adb shell locksettings
+ * set-disabled". If suppressed, we'll ignore all signals that would typically result in showing
+ * the keyguard, regardless of the value of [isKeyguardEnabled].
+ *
+ * It's extremely confusing to have [isKeyguardEnabled] not be the inverse of "is lockscreen
+ * disabled", so this method intentionally re-terms it as "suppressed".
+ *
+ * Note that if the lockscreen is currently showing when it's suppressed, it will remain visible
+ * until it's unlocked, at which point it will never re-appear until suppression is removed.
+ */
+ suspend fun isKeyguardSuppressed(
+ userId: Int = selectedUserInteractor.getSelectedUserId()
+ ): Boolean {
+ // isLockScreenDisabled returns true whenever keyguard is not enabled, even if the adb
+ // command was not used to disable/suppress the lockscreen. To make these booleans as clear
+ // as possible, only return true if keyguard is suppressed when it otherwise would have
+ // been enabled.
+ return withContext(backgroundDispatcher) {
+ isKeyguardEnabled.value && lockPatternUtils.isLockScreenDisabled(userId)
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardLockWhileAwakeInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardLockWhileAwakeInteractor.kt
index 0ab3e5c0927f..ce84e71a3562 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardLockWhileAwakeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardLockWhileAwakeInteractor.kt
@@ -16,27 +16,16 @@
package com.android.systemui.keyguard.domain.interactor
-import android.os.Bundle
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
-/**
- * Emitted when we receive a [KeyguardLockWhileAwakeInteractor.onKeyguardServiceDoKeyguardTimeout]
- * call.
- *
- * Includes a timestamp so it's not conflated by the StateFlow.
- */
-data class KeyguardTimeoutWhileAwakeEvent(val timestamp: Long, val options: Bundle?)
-
/** The reason we're locking while awake, used for logging. */
enum class LockWhileAwakeReason(private val logReason: String) {
LOCKDOWN("Lockdown initiated."),
@@ -71,10 +60,8 @@ class KeyguardLockWhileAwakeInteractor
constructor(
biometricSettingsRepository: BiometricSettingsRepository,
keyguardEnabledInteractor: KeyguardEnabledInteractor,
+ keyguardServiceLockNowInteractor: KeyguardServiceLockNowInteractor,
) {
- /** Emits whenever a timeout event is received by [KeyguardService]. */
- private val timeoutEvents: MutableStateFlow<KeyguardTimeoutWhileAwakeEvent?> =
- MutableStateFlow(null)
/** Emits whenever the current user is in lockdown mode. */
private val inLockdown: Flow<LockWhileAwakeReason> =
@@ -97,25 +84,19 @@ constructor(
/** Emits whenever we should lock while the screen is on, for any reason. */
val lockWhileAwakeEvents: Flow<LockWhileAwakeReason> =
merge(
- inLockdown,
- keyguardReenabled,
- timeoutEvents.filterNotNull().map {
- LockWhileAwakeReason.KEYGUARD_TIMEOUT_WHILE_SCREEN_ON
- },
+ // We're in lockdown, and the keyguard is enabled. If the keyguard is disabled, the
+ // lockdown button is hidden in the UI, but it's still possible to trigger lockdown in
+ // tests.
+ inLockdown
+ .filter { keyguardEnabledInteractor.isKeyguardEnabledAndNotSuppressed() }
+ .map { LockWhileAwakeReason.LOCKDOWN },
+ // The keyguard was re-enabled, and it was showing when it was originally disabled.
+ // Tests currently expect that if the keyguard is re-enabled, it will show even if it's
+ // suppressed, so we don't check for isKeyguardEnabledAndNotSuppressed() on this flow.
+ keyguardReenabled.map { LockWhileAwakeReason.KEYGUARD_REENABLED },
+ // KeyguardService says we need to lock now, and the lockscreen is enabled.
+ keyguardServiceLockNowInteractor.lockNowEvents
+ .filter { keyguardEnabledInteractor.isKeyguardEnabledAndNotSuppressed() }
+ .map { LockWhileAwakeReason.KEYGUARD_TIMEOUT_WHILE_SCREEN_ON },
)
-
- /**
- * Called by [KeyguardService] when it receives a doKeyguardTimeout() call. This indicates that
- * the device locked while the screen was on.
- *
- * [options] appears to be no longer used, but we'll keep it in this interactor in case that
- * turns out not to be true.
- */
- fun onKeyguardServiceDoKeyguardTimeout(options: Bundle?) {
- timeoutEvents.value =
- KeyguardTimeoutWhileAwakeEvent(
- timestamp = System.currentTimeMillis(),
- options = options,
- )
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardServiceLockNowInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardServiceLockNowInteractor.kt
new file mode 100644
index 000000000000..9ed53ea74316
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardServiceLockNowInteractor.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.annotation.SuppressLint
+import android.os.Bundle
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.launch
+
+/**
+ * Emitted when we receive a [KeyguardServiceLockNowInteractor.onKeyguardServiceDoKeyguardTimeout]
+ * call.
+ */
+data class KeyguardLockNowEvent(val options: Bundle?)
+
+/**
+ * Logic around requests by [KeyguardService] to lock the device right now, even though the device
+ * is awake and not going to sleep.
+ *
+ * This can happen if WM#lockNow() is called, or if the screen is forced to stay awake but the lock
+ * timeout elapses.
+ *
+ * This is not the only way for the device to lock while the screen is on. The other cases, which do
+ * not directly involve [KeyguardService], are handled in [KeyguardLockWhileAwakeInteractor].
+ */
+@SysUISingleton
+class KeyguardServiceLockNowInteractor
+@Inject
+constructor(@Background val backgroundScope: CoroutineScope) {
+
+ /**
+ * Emits whenever [KeyguardService] receives a call that indicates we should lock the device
+ * right now, even though the device is awake and not going to sleep.
+ *
+ * WARNING: This is only one of multiple reasons the device might need to lock while not going
+ * to sleep. Unless you're dealing with keyguard internals that specifically need to know that
+ * we're locking due to a call to doKeyguardTimeout, use
+ * [KeyguardLockWhileAwakeInteractor.lockWhileAwakeEvents].
+ *
+ * This is fundamentally an event flow, hence the SharedFlow.
+ */
+ @SuppressLint("SharedFlowCreation")
+ val lockNowEvents: MutableSharedFlow<KeyguardLockNowEvent> = MutableSharedFlow()
+
+ /**
+ * Called by [KeyguardService] when it receives a doKeyguardTimeout() call. This indicates that
+ * the device locked while the screen was on.
+ *
+ * [options] appears to be no longer used, but we'll keep it in this interactor in case that
+ * turns out not to be true.
+ */
+ fun onKeyguardServiceDoKeyguardTimeout(options: Bundle?) {
+ backgroundScope.launch { lockNowEvents.emit(KeyguardLockNowEvent(options = options)) }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractor.kt
index fbc7e2aecbdf..8641dfa5a183 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractor.kt
@@ -25,6 +25,7 @@ import android.content.Intent
import android.content.IntentFilter
import android.provider.Settings
import android.provider.Settings.Secure
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -48,20 +49,21 @@ import javax.inject.Inject
import kotlin.math.max
import kotlin.math.min
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.distinctUntilChangedBy
import kotlinx.coroutines.flow.map
-import com.android.app.tracing.coroutines.launchTraced as launch
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onStart
/**
* Logic related to the ability to wake directly to GONE from asleep (AOD/DOZING), without going
* through LOCKSCREEN or a BOUNCER state.
*
* This is possible in the following scenarios:
- * - The lockscreen is disabled, either from an app request (SUW does this), or by the security
+ * - The keyguard is not enabled, either from an app request (SUW does this), or by the security
* "None" setting.
+ * - The keyguard was suppressed via adb.
* - A biometric authentication event occurred while we were asleep (fingerprint auth, etc). This
* specifically is referred to throughout the codebase as "wake and unlock".
* - The screen timed out, but the "lock after screen timeout" duration has not elapsed.
@@ -86,43 +88,44 @@ constructor(
private val lockPatternUtils: LockPatternUtils,
private val systemSettings: SystemSettings,
private val selectedUserInteractor: SelectedUserInteractor,
+ keyguardEnabledInteractor: KeyguardEnabledInteractor,
+ keyguardServiceLockNowInteractor: KeyguardServiceLockNowInteractor,
) {
/**
- * Whether the lockscreen was disabled as of the last wake/sleep event, according to
- * LockPatternUtils.
- *
- * This will always be true if [repository.isKeyguardServiceEnabled]=false, but it can also be
- * true when the keyguard service is enabled if the lockscreen has been disabled via adb using
- * the `adb shell locksettings set-disabled true` command, which is often done in tests.
- *
- * Unlike keyguardServiceEnabled, changes to this value should *not* immediately show or hide
- * the keyguard. If the lockscreen is disabled in this way, it will just not show on the next
- * sleep/wake.
+ * Whether the keyguard was suppressed as of the most recent wakefulness event or lockNow
+ * command. Keyguard suppression can only be queried (there is no callback available), and
+ * legacy code only queried the value in onStartedGoingToSleep and doKeyguardTimeout. Tests now
+ * depend on that behavior, so for now, we'll replicate it here.
*/
- private val isLockscreenDisabled: Flow<Boolean> =
- powerInteractor.isAwake.map { isLockscreenDisabled() }
+ private val shouldSuppressKeyguard =
+ merge(powerInteractor.isAwake, keyguardServiceLockNowInteractor.lockNowEvents)
+ .map { keyguardEnabledInteractor.isKeyguardSuppressed() }
+ // Default to false, so that flows that combine this one emit prior to the first
+ // wakefulness emission.
+ .onStart { emit(false) }
/**
* Whether we can wake from AOD/DOZING directly to GONE, bypassing LOCKSCREEN/BOUNCER states.
*
* This is possible in the following cases:
* - Keyguard is disabled, either from an app request or from security being set to "None".
+ * - Keyguard is suppressed, via adb locksettings.
* - We're wake and unlocking (fingerprint auth occurred while asleep).
* - We're allowed to ignore auth and return to GONE, due to timeouts not elapsing.
*/
val canWakeDirectlyToGone =
combine(
repository.isKeyguardEnabled,
- isLockscreenDisabled,
+ shouldSuppressKeyguard,
repository.biometricUnlockState,
repository.canIgnoreAuthAndReturnToGone,
) {
keyguardEnabled,
- isLockscreenDisabled,
+ shouldSuppressKeyguard,
biometricUnlockState,
canIgnoreAuthAndReturnToGone ->
- (!keyguardEnabled || isLockscreenDisabled) ||
+ (!keyguardEnabled || shouldSuppressKeyguard) ||
BiometricUnlockMode.isWakeAndUnlock(biometricUnlockState.mode) ||
canIgnoreAuthAndReturnToGone
}
@@ -186,9 +189,9 @@ constructor(
.sample(
transitionInteractor.isCurrentlyIn(
Scenes.Gone,
- stateWithoutSceneContainer = KeyguardState.GONE
+ stateWithoutSceneContainer = KeyguardState.GONE,
),
- ::Pair
+ ::Pair,
)
.collect { (wakefulness, finishedInGone) ->
// Save isAwake for use in onDreamingStarted/onDreamingStopped.
@@ -260,7 +263,7 @@ constructor(
delayedActionFilter,
SYSTEMUI_PERMISSION,
null /* scheduler */,
- Context.RECEIVER_EXPORTED_UNAUDITED
+ Context.RECEIVER_EXPORTED_UNAUDITED,
)
}
@@ -282,7 +285,7 @@ constructor(
context,
0,
intent,
- PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE
+ PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE,
)
val time = systemClock.elapsedRealtime() + getCanIgnoreAuthAndReturnToGoneDuration()
@@ -311,16 +314,6 @@ constructor(
}
/**
- * Returns whether the lockscreen is disabled, either because the keyguard service is disabled
- * or because an adb command has disabled the lockscreen.
- */
- private fun isLockscreenDisabled(
- userId: Int = selectedUserInteractor.getSelectedUserId()
- ): Boolean {
- return lockPatternUtils.isLockScreenDisabled(userId)
- }
-
- /**
* Returns the duration within which we can return to GONE without auth after a screen timeout
* (or power button press, if lock instantly is disabled).
*
@@ -336,7 +329,7 @@ constructor(
.getIntForUser(
Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT,
KEYGUARD_CAN_IGNORE_AUTH_DURATION,
- userId
+ userId,
)
.toLong()
@@ -352,7 +345,7 @@ constructor(
.getIntForUser(
Settings.System.SCREEN_OFF_TIMEOUT,
KEYGUARD_DISPLAY_TIMEOUT_DELAY_DEFAULT,
- userId
+ userId,
)
.toLong()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
index 8622ffc04601..160380bb09bc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
@@ -19,6 +19,7 @@ package com.android.systemui.keyguard.ui.view.layout.sections
import android.content.Context
import android.view.View
+import android.view.ViewGroup
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
@@ -47,11 +48,15 @@ constructor(
visibility = View.GONE
}
}
+
override fun addViews(constraintLayout: ConstraintLayout) {
if (!MigrateClocksToBlueprint.isEnabled) {
return
}
-
+ if (emptyView.parent != null) {
+ // As emptyView is lazy, it might be already attached.
+ (emptyView.parent as? ViewGroup)?.removeView(emptyView)
+ }
constraintLayout.addView(emptyView)
burnInLayer =
AodBurnInLayer(context).apply {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 80ac2fcd4aa4..0a4e8c660761 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -18,6 +18,7 @@ package com.android.systemui.navigationbar.gestural;
import static android.content.pm.ActivityInfo.CONFIG_FONT_SCALE;
import static android.view.InputDevice.SOURCE_MOUSE;
import static android.view.InputDevice.SOURCE_TOUCHPAD;
+import static android.view.MotionEvent.TOOL_TYPE_FINGER;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION;
import static com.android.systemui.Flags.edgebackGestureHandlerGetRunningTasksBackground;
@@ -216,6 +217,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
private final int mDisplayId;
private final UiThreadContext mUiThreadContext;
+ private final Handler mBgHandler;
private final Executor mBackgroundExecutor;
private final Rect mPipExcludedBounds = new Rect();
@@ -378,11 +380,14 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
@Override
public void onInputDeviceAdded(int deviceId) {
if (isTrackpadDevice(deviceId)) {
- boolean wasEmpty = mTrackpadsConnected.isEmpty();
- mTrackpadsConnected.add(deviceId);
- if (wasEmpty) {
- update();
- }
+ // This updates the gesture handler state and should be running on the main thread.
+ mUiThreadContext.getHandler().post(() -> {
+ boolean wasEmpty = mTrackpadsConnected.isEmpty();
+ mTrackpadsConnected.add(deviceId);
+ if (wasEmpty) {
+ update();
+ }
+ });
}
}
@@ -391,10 +396,13 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
@Override
public void onInputDeviceRemoved(int deviceId) {
- mTrackpadsConnected.remove(deviceId);
- if (mTrackpadsConnected.isEmpty()) {
- update();
- }
+ // This updates the gesture handler state and should be running on the main thread.
+ mUiThreadContext.getHandler().post(() -> {
+ mTrackpadsConnected.remove(deviceId);
+ if (mTrackpadsConnected.isEmpty()) {
+ update();
+ }
+ });
}
private void update() {
@@ -408,12 +416,12 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
}
private boolean isTrackpadDevice(int deviceId) {
+ // This is a blocking binder call that should run on a bg thread.
InputDevice inputDevice = mInputManager.getInputDevice(deviceId);
if (inputDevice == null) {
return false;
}
- return inputDevice.getSources() == (InputDevice.SOURCE_MOUSE
- | InputDevice.SOURCE_TOUCHPAD);
+ return inputDevice.getSources() == (SOURCE_MOUSE | SOURCE_TOUCHPAD);
}
};
@@ -457,6 +465,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
mDisplayId = context.getDisplayId();
mUiThreadContext = uiThreadContext;
mBackgroundExecutor = backgroundExecutor;
+ mBgHandler = bgHandler;
mUserTracker = userTracker;
mOverviewProxyService = overviewProxyService;
mSysUiState = sysUiState;
@@ -611,9 +620,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
mIsAttached = true;
mOverviewProxyService.addCallback(mQuickSwitchListener);
mSysUiState.addCallback(mSysUiStateCallback);
- mInputManager.registerInputDeviceListener(
- mInputDeviceListener,
- mUiThreadContext.getHandler());
+ mInputManager.registerInputDeviceListener(mInputDeviceListener, mBgHandler);
int[] inputDevices = mInputManager.getInputDeviceIds();
for (int inputDeviceId : inputDevices) {
mInputDeviceListener.onInputDeviceAdded(inputDeviceId);
@@ -1089,8 +1096,8 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
&& isValidTrackpadBackGesture(true /* isTrackpadEvent */);
} else {
mAllowGesture = isBackAllowedCommon && !mUsingThreeButtonNav && isWithinInsets
- && isWithinTouchRegion((int) ev.getX(), (int) ev.getY())
- && !isButtonPressFromTrackpad(ev);
+ && isWithinTouchRegion((int) ev.getX(), (int) ev.getY())
+ && !isButtonPressFromTrackpad(ev);
}
if (mAllowGesture) {
mEdgeBackPlugin.setIsLeftPanel(mIsOnLeftEdge);
@@ -1202,10 +1209,8 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
}
private boolean isButtonPressFromTrackpad(MotionEvent ev) {
- // We don't allow back for button press from the trackpad, and yet we do with a mouse.
- int sources = InputManager.getInstance().getInputDevice(ev.getDeviceId()).getSources();
- int sourceTrackpad = (SOURCE_MOUSE | SOURCE_TOUCHPAD);
- return (sources & sourceTrackpad) == sourceTrackpad && ev.getButtonState() != 0;
+ return ev.getSource() == (SOURCE_MOUSE | SOURCE_TOUCHPAD)
+ && ev.getToolType(ev.getActionIndex()) == TOOL_TYPE_FINGER;
}
private void dispatchToBackAnimation(MotionEvent event) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java
index 40613c0edc68..96c0cac53908 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java
@@ -149,7 +149,6 @@ import com.android.systemui.statusbar.CommandQueue.Callbacks;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.data.repository.LightBarControllerStore;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
import com.android.systemui.statusbar.phone.AutoHideController;
import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -259,7 +258,8 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
private boolean mTransientShownFromGestureOnSystemBar;
private int mNavBarMode = NAV_BAR_MODE_3BUTTON;
private LightBarController mLightBarController;
- private final LightBarControllerStore mLightBarControllerStore;
+ private final LightBarController mMainLightBarController;
+ private final LightBarController.Factory mLightBarControllerFactory;
private AutoHideController mAutoHideController;
private final AutoHideController mMainAutoHideController;
private final AutoHideController.Factory mAutoHideControllerFactory;
@@ -580,7 +580,8 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
@Background Executor bgExecutor,
UiEventLogger uiEventLogger,
NavBarHelper navBarHelper,
- LightBarControllerStore lightBarControllerStore,
+ LightBarController mainLightBarController,
+ LightBarController.Factory lightBarControllerFactory,
AutoHideController mainAutoHideController,
AutoHideController.Factory autoHideControllerFactory,
Optional<TelecomManager> telecomManagerOptional,
@@ -627,7 +628,8 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
mUiEventLogger = uiEventLogger;
mNavBarHelper = navBarHelper;
mNotificationShadeDepthController = notificationShadeDepthController;
- mLightBarControllerStore = lightBarControllerStore;
+ mMainLightBarController = mainLightBarController;
+ mLightBarControllerFactory = lightBarControllerFactory;
mMainAutoHideController = mainAutoHideController;
mAutoHideControllerFactory = autoHideControllerFactory;
mTelecomManagerOptional = telecomManagerOptional;
@@ -840,7 +842,8 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
// Unfortunately, we still need it because status bar needs LightBarController
// before notifications creation. We cannot directly use getLightBarController()
// from NavigationBarFragment directly.
- LightBarController lightBarController = mLightBarControllerStore.forDisplay(mDisplayId);
+ LightBarController lightBarController = mIsOnDefaultDisplay
+ ? mMainLightBarController : mLightBarControllerFactory.create(mContext);
setLightBarController(lightBarController);
// TODO(b/118592525): to support multi-display, we start to add something which is
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
index ca28ab3e6ce3..2928ad117922 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
@@ -75,6 +75,8 @@ fun SceneScope.QuickQuickSettings(
coroutineScope = scope,
bounceableInfo = bounceables.bounceableInfo(it, spanIndex, column, columns),
tileHapticsViewModelFactoryProvider = viewModel.tileHapticsViewModelFactoryProvider,
+ // There should be no QuickQuickSettings when the details view is enabled.
+ detailsViewModel = null,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileDetails.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileDetails.kt
new file mode 100644
index 000000000000..1bfbbe1964dc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileDetails.kt
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.ui.compose
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.filled.ArrowBack
+import androidx.compose.material.icons.filled.Settings
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.LocalContentColor
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.em
+import com.android.systemui.qs.flags.QsDetailedView
+import com.android.systemui.qs.panels.ui.viewmodel.DetailsViewModel
+
+@Composable
+fun TileDetails(detailsViewModel: DetailsViewModel) {
+
+ if (!QsDetailedView.isEnabled) {
+ throw IllegalStateException("QsDetailedView should be enabled")
+ }
+
+ val tileDetailedViewModel = detailsViewModel.activeTileDetails ?: return
+
+ DisposableEffect(Unit) { onDispose { detailsViewModel.closeDetailedView() } }
+
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ // The height of the details view is TBD.
+ .fillMaxHeight()
+ ) {
+ CompositionLocalProvider(
+ value = LocalContentColor provides MaterialTheme.colorScheme.onSurfaceVariant
+ ) {
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+
+ IconButton(
+ onClick = { detailsViewModel.closeDetailedView() },
+ modifier = Modifier
+ .align(Alignment.CenterVertically)
+ .height(TileDetailsDefaults.IconHeight)
+ .padding(start = TileDetailsDefaults.IconPadding),
+ ) {
+ Icon(
+ imageVector = Icons.AutoMirrored.Filled.ArrowBack,
+ // Description is TBD
+ contentDescription = "Back to QS panel",
+ )
+ }
+ Text(
+ text = tileDetailedViewModel.getTitle(),
+ modifier = Modifier
+ .align(Alignment.CenterVertically),
+ textAlign = TextAlign.Center,
+ style = MaterialTheme.typography.titleLarge
+ )
+ IconButton(
+ onClick = { tileDetailedViewModel.clickOnSettingsButton() },
+ modifier = Modifier
+ .align(Alignment.CenterVertically)
+ .height(TileDetailsDefaults.IconHeight)
+ .padding(end = TileDetailsDefaults.IconPadding),
+ ) {
+ Icon(
+ imageVector = Icons.Default.Settings,
+ // Description is TBD
+ contentDescription = "Go to Settings",
+ )
+ }
+ }
+ Text(
+ text = tileDetailedViewModel.getSubTitle(),
+ modifier = Modifier
+ .fillMaxWidth(),
+ textAlign = TextAlign.Center,
+ style = MaterialTheme.typography.titleSmall
+
+ )
+ }
+ tileDetailedViewModel.GetContentView()
+ }
+}
+
+private object TileDetailsDefaults {
+ val IconHeight = 48.dp
+ val IconPadding = 4.dp
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt
index cb857ae5efc6..8fd99a52eceb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt
@@ -36,6 +36,7 @@ import com.android.systemui.qs.panels.ui.compose.PaginatableGridLayout
import com.android.systemui.qs.panels.ui.compose.bounceableInfo
import com.android.systemui.qs.panels.ui.compose.rememberEditListState
import com.android.systemui.qs.panels.ui.viewmodel.BounceableTileViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.DetailsViewModel
import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
import com.android.systemui.qs.panels.ui.viewmodel.IconTilesViewModel
import com.android.systemui.qs.panels.ui.viewmodel.InfiniteGridViewModel
@@ -49,6 +50,7 @@ import javax.inject.Inject
class InfiniteGridLayout
@Inject
constructor(
+ private val detailsViewModel: DetailsViewModel,
private val iconTilesViewModel: IconTilesViewModel,
private val viewModelFactory: InfiniteGridViewModel.Factory,
private val tileHapticsViewModelFactoryProvider: TileHapticsViewModelFactoryProvider,
@@ -100,6 +102,7 @@ constructor(
tileHapticsViewModelFactoryProvider = tileHapticsViewModelFactoryProvider,
coroutineScope = scope,
bounceableInfo = bounceables.bounceableInfo(it, spanIndex, column, columns),
+ detailsViewModel = detailsViewModel,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
index 0a80a19871fd..abdf923ebe73 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
@@ -77,10 +77,12 @@ import com.android.systemui.haptics.msdl.qs.TileHapticsViewModel
import com.android.systemui.haptics.msdl.qs.TileHapticsViewModelFactoryProvider
import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.qs.flags.QsDetailedView
import com.android.systemui.qs.panels.ui.compose.BounceableInfo
import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.InactiveCornerRadius
import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.TileHeight
import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.longPressLabel
+import com.android.systemui.qs.panels.ui.viewmodel.DetailsViewModel
import com.android.systemui.qs.panels.ui.viewmodel.TileUiState
import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
import com.android.systemui.qs.panels.ui.viewmodel.toUiState
@@ -121,6 +123,7 @@ fun Tile(
bounceableInfo: BounceableInfo,
tileHapticsViewModelFactoryProvider: TileHapticsViewModelFactoryProvider,
modifier: Modifier = Modifier,
+ detailsViewModel: DetailsViewModel?,
) {
val state by tile.state.collectAsStateWithLifecycle(tile.currentState)
val currentBounceableInfo by rememberUpdatedState(bounceableInfo)
@@ -163,12 +166,20 @@ fun Tile(
.takeIf { uiState.handlesLongClick }
TileContainer(
onClick = {
- tile.onClick(expandable)
- hapticsViewModel?.setTileInteractionState(
- TileHapticsViewModel.TileInteractionState.CLICKED
- )
- if (uiState.accessibilityUiState.toggleableState != null) {
- coroutineScope.launch { currentBounceableInfo.bounceable.animateBounce() }
+ var hasDetails = false
+ if (QsDetailedView.isEnabled) {
+ hasDetails = detailsViewModel?.onTileClicked(tile.spec) == true
+ }
+ if (!hasDetails) {
+ // For those tile's who doesn't have a detailed view, process with their
+ // `onClick` behavior.
+ tile.onClick(expandable)
+ hapticsViewModel?.setTileInteractionState(
+ TileHapticsViewModel.TileInteractionState.CLICKED
+ )
+ if (uiState.accessibilityUiState.toggleableState != null) {
+ coroutineScope.launch { currentBounceableInfo.bounceable.animateBounce() }
+ }
}
},
onLongClick = longClick,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/ResizingState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/ResizingState.kt
index b27c08077834..1113053e36d2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/ResizingState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/ResizingState.kt
@@ -73,6 +73,11 @@ class ResizingState(tileSpec: TileSpec, startsAsIcon: Boolean) {
anchoredDraggableState.animateTo(if (isIcon) QSDragAnchor.Icon else QSDragAnchor.Large)
}
+ suspend fun toggleCurrentValue() {
+ val isIcon = anchoredDraggableState.currentValue == QSDragAnchor.Icon
+ updateCurrentValue(!isIcon)
+ }
+
fun progress(): Float = anchoredDraggableState.progress(QSDragAnchor.Icon, QSDragAnchor.Large)
/**
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt
index a187ff135cbb..c1545e1263db 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt
@@ -21,6 +21,7 @@ import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.spring
import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.anchoredDraggable
import androidx.compose.foundation.layout.Box
@@ -32,6 +33,7 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawWithContent
@@ -50,6 +52,7 @@ import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults
import com.android.systemui.qs.panels.ui.compose.selection.SelectionDefaults.ResizingDotSize
import com.android.systemui.qs.panels.ui.compose.selection.SelectionDefaults.SelectedBorderWidth
import kotlin.math.roundToInt
+import kotlinx.coroutines.launch
/**
* Places a dot to handle resizing drag events. Use this on tiles to resize.
@@ -88,6 +91,7 @@ private fun ResizingHandle(enabled: Boolean, state: ResizingState, modifier: Mod
// Manually creating the touch target around the resizing dot to ensure that the next tile
// does not receive the touch input accidentally.
val minTouchTargetSize = LocalMinimumInteractiveComponentSize.current
+ val scope = rememberCoroutineScope()
Box(
modifier
.layout { measurable, constraints ->
@@ -106,6 +110,9 @@ private fun ResizingHandle(enabled: Boolean, state: ResizingState, modifier: Mod
state = state.anchoredDraggableState,
orientation = Orientation.Horizontal,
)
+ .clickable(enabled = enabled, interactionSource = null, indication = null) {
+ scope.launch { state.toggleCurrentValue() }
+ }
) {
ResizingDot(enabled = enabled, modifier = Modifier.align(Alignment.Center))
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModel.kt
new file mode 100644
index 000000000000..d8361f544bdc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModel.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.ui.viewmodel
+
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.plugins.qs.TileDetailsViewModel
+import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import javax.inject.Inject
+import kotlinx.coroutines.flow.StateFlow
+
+@SysUISingleton
+class DetailsViewModel
+@Inject constructor(val currentTilesInteractor: CurrentTilesInteractor) {
+
+ /**
+ * The current active [TileDetailsViewModel]. If it's `null`, it means the qs overlay is not
+ * showing any details view. It changes when [onTileClicked] and [closeDetailedView].
+ */
+ private val _activeTileDetails = mutableStateOf<TileDetailsViewModel?>(null)
+ val activeTileDetails by _activeTileDetails
+
+ /**
+ * Update the active [TileDetailsViewModel] to `null`.
+ * @see activeTileDetails
+ */
+ fun closeDetailedView() {
+ _activeTileDetails.value = null
+ }
+
+ /**
+ * Update the active [TileDetailsViewModel] to the `spec`'s corresponding view model.
+ * Return if the [TileDetailsViewModel] is successfully found.
+ * @see activeTileDetails
+ */
+ fun onTileClicked(spec: TileSpec?): Boolean {
+ if (spec == null) {
+ _activeTileDetails.value = null
+ return false
+ }
+
+ _activeTileDetails.value = currentTilesInteractor
+ .currentQSTiles
+ .firstOrNull { it.tileSpec == spec.spec }
+ ?.detailsViewModel
+
+ return _activeTileDetails.value != null
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTileNewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTileNewImpl.kt
index 42ef0cd3abb7..7225800e25e3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTileNewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTileNewImpl.kt
@@ -28,11 +28,14 @@ import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.plugins.qs.TileDetailsViewModel
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.qs.QSHost
import com.android.systemui.qs.QsEventLogger
+import com.android.systemui.qs.flags.QsDetailedView
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.qs.tiles.dialog.InternetDetailsViewModel
import com.android.systemui.qs.tiles.dialog.InternetDialogManager
import com.android.systemui.qs.tiles.dialog.WifiStateWorker
import com.android.systemui.res.R
@@ -90,6 +93,9 @@ constructor(
}
override fun handleClick(expandable: Expandable?) {
+ if (QsDetailedView.isEnabled) {
+ return
+ }
mainHandler.post {
internetDialogManager.create(
aboveStatusBar = true,
@@ -100,6 +106,10 @@ constructor(
}
}
+ override fun getDetailsViewModel(): TileDetailsViewModel {
+ return InternetDetailsViewModel { longClick(null) }
+ }
+
override fun secondaryClick(expandable: Expandable?) {
// TODO(b/358352265): Figure out the correct action for the secondary click
// Toggle wifi
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailedViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailedViewModel.kt
new file mode 100644
index 000000000000..f239a179d79a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailedViewModel.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.dialog
+
+import android.view.LayoutInflater
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.viewinterop.AndroidView
+import com.android.systemui.plugins.qs.TileDetailsViewModel
+import com.android.systemui.res.R
+
+class InternetDetailsViewModel(
+ onLongClick: () -> Unit,
+) : TileDetailsViewModel() {
+ private val _onLongClick = onLongClick
+
+ @Composable
+ override fun GetContentView() {
+ AndroidView(
+ modifier = Modifier.fillMaxWidth().fillMaxHeight(),
+ factory = { context ->
+ // Inflate with the existing dialog xml layout
+ LayoutInflater.from(context)
+ .inflate(R.layout.internet_connectivity_dialog, null)
+ // TODO: b/377388104 - Implement the internet details view
+ },
+ )
+ }
+
+ override fun clickOnSettingsButton() {
+ _onLongClick()
+ }
+
+ override fun getTitle(): String {
+ // TODO: b/377388104 Update the placeholder text
+ return "Internet"
+ }
+
+ override fun getSubTitle(): String {
+ // TODO: b/377388104 Update the placeholder text
+ return "Tab a network to connect"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractor.kt
index 8f870d468997..4806c3f83224 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractor.kt
@@ -18,7 +18,7 @@ package com.android.systemui.qs.tiles.impl.custom.domain.interactor
import android.os.UserHandle
import android.service.quicksettings.Tile
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
@@ -28,6 +28,7 @@ import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTilePacka
import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel
import com.android.systemui.qs.tiles.impl.di.QSTileScope
import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -44,7 +45,6 @@ import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.shareIn
-import com.android.app.tracing.coroutines.launchTraced as launch
@QSTileScope
@OptIn(ExperimentalCoroutinesApi::class)
@@ -64,7 +64,7 @@ constructor(
private val bindingFlow =
mutableUserFlow
.flatMapLatest { user ->
- ConflatedCallbackFlow.conflatedCallbackFlow {
+ conflatedCallbackFlow {
serviceInteractor.setUser(user)
// Wait for the CustomTileInteractor to become initialized first, because
@@ -79,7 +79,7 @@ constructor(
defaultsRepository.requestNewDefaults(
user,
tileSpec.componentName,
- true
+ true,
)
}
.launchIn(this)
@@ -99,7 +99,7 @@ constructor(
override fun tileData(
user: UserHandle,
- triggers: Flow<DataUpdateTrigger>
+ triggers: Flow<DataUpdateTrigger>,
): Flow<CustomTileDataModel> {
tileScope.launch { mutableUserFlow.emit(user) }
return bindingFlow.combine(triggers) { _, _ -> }.flatMapLatest { dataFlow(user) }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
index ab3862b75ee8..f9a1ad5d8424 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
@@ -25,6 +25,7 @@ import com.android.systemui.Dumpable
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.UiBackground
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.qs.QSHost
import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIcon
@@ -35,14 +36,17 @@ import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import java.io.PrintWriter
import java.util.concurrent.CopyOnWriteArraySet
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.collectIndexed
import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.takeWhile
+import kotlinx.coroutines.launch
// TODO(b/http://b/299909989): Use QSTileViewModel directly after the rollout
class QSTileViewModelAdapter
@@ -51,6 +55,7 @@ constructor(
@Application private val applicationScope: CoroutineScope,
private val qsHost: QSHost,
@Assisted private val qsTileViewModel: QSTileViewModel,
+ @UiBackground private val uiBgDispatcher: CoroutineDispatcher,
) : QSTile, Dumpable {
private val context
@@ -162,19 +167,25 @@ constructor(
override fun setListening(client: Any?, listening: Boolean) {
client ?: return
if (listening) {
- val clientWasNotAlreadyListening = listeningClients.add(client)
- if (clientWasNotAlreadyListening && listeningClients.size == 1) {
- stateJob =
- qsTileViewModel.state
- .filterNotNull()
- .map { mapState(context, it, qsTileViewModel.config) }
- .onEach { legacyState ->
- val changed = legacyState.copyTo(cachedState)
- if (changed) {
- callbacks.forEach { it.onStateChanged(legacyState) }
+ applicationScope.launch(uiBgDispatcher) {
+ val shouldStartMappingJob =
+ listeningClients.add(client) // new client
+ && listeningClients.size == 1 // first client
+
+ if (shouldStartMappingJob) {
+ stateJob =
+ qsTileViewModel.state
+ .filterNotNull()
+ .map { mapState(context, it, qsTileViewModel.config) }
+ .onEach { legacyState ->
+ val changed = legacyState.copyTo(cachedState)
+ if (changed) {
+ callbacks.forEach { it.onStateChanged(legacyState) }
+ }
}
- }
- .launchIn(applicationScope)
+ .flowOn(uiBgDispatcher)
+ .launchIn(applicationScope)
+ }
}
} else {
listeningClients.remove(client)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt
index da175c9120ba..62b120332289 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt
@@ -18,6 +18,7 @@ package com.android.systemui.qs.ui.viewmodel
import com.android.systemui.brightness.ui.viewmodel.BrightnessSliderViewModel
import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.qs.panels.ui.viewmodel.DetailsViewModel
import com.android.systemui.qs.panels.ui.viewmodel.EditModeViewModel
import com.android.systemui.qs.panels.ui.viewmodel.QuickQuickSettingsViewModel
import com.android.systemui.qs.panels.ui.viewmodel.TileGridViewModel
@@ -36,6 +37,7 @@ constructor(
@Assisted supportsBrightnessMirroring: Boolean,
val tileGridViewModel: TileGridViewModel,
val editModeViewModel: EditModeViewModel,
+ val detailsViewModel: DetailsViewModel,
) : ExclusiveActivatable() {
val brightnessSliderViewModel =
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt
index ba789a01d285..3a07ce959bb3 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt
@@ -16,6 +16,7 @@
package com.android.systemui.scene.domain.interactor
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
@@ -29,9 +30,9 @@ import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.statusbar.NotificationPresenter
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
import com.android.systemui.statusbar.notification.init.NotificationsController
import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
-import com.android.systemui.statusbar.policy.HeadsUpManager
import javax.inject.Inject
import javax.inject.Provider
import kotlinx.coroutines.CoroutineScope
@@ -44,7 +45,6 @@ import kotlinx.coroutines.flow.flatMapConcat
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
-import com.android.app.tracing.coroutines.launchTraced as launch
/** Business logic about the visibility of various parts of the window root view. */
@OptIn(ExperimentalCoroutinesApi::class)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
index 49ceba834dd4..31780a56f7f0 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
@@ -37,9 +37,11 @@ import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.compose.theme.PlatformTheme
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.Flags
+import com.android.systemui.Flags.communalHubOnMobile
import com.android.systemui.ambient.touch.TouchMonitor
import com.android.systemui.ambient.touch.dagger.AmbientTouchComponent
import com.android.systemui.communal.dagger.Communal
@@ -70,7 +72,6 @@ import java.util.function.Consumer
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
-import com.android.app.tracing.coroutines.launchTraced as launch
/**
* Controller that's responsible for the glanceable hub container view and its touch handling.
@@ -513,14 +514,19 @@ constructor(
val touchOnUmo = keyguardMediaController.isWithinMediaViewBounds(ev.x.toInt(), ev.y.toInt())
val touchOnSmartspace =
lockscreenSmartspaceController.isWithinSmartspaceBounds(ev.x.toInt(), ev.y.toInt())
- if (!hubShowing && (touchOnNotifications || touchOnUmo || touchOnSmartspace)) {
+ val glanceableHubV2 = communalHubOnMobile()
+ if (
+ !hubShowing &&
+ (touchOnNotifications || touchOnUmo || touchOnSmartspace || glanceableHubV2)
+ ) {
logger.d({
"Lockscreen touch ignored: touchOnNotifications: $bool1, touchOnUmo: $bool2, " +
- "touchOnSmartspace: $bool3"
+ "touchOnSmartspace: $bool3, glanceableHubV2: $bool4"
}) {
bool1 = touchOnNotifications
bool2 = touchOnUmo
bool3 = touchOnSmartspace
+ bool4 = glanceableHubV2
}
return false
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 42a756c27de7..88522d559c30 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -186,13 +186,15 @@ import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.ConversationNotificationManager;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
-import com.android.systemui.statusbar.notification.HeadsUpTouchHelper;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpTouchHelper;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.PropertyAnimator;
import com.android.systemui.statusbar.notification.ViewGroupFadeHelper;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor;
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.OnHeadsUpChangedListener;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -217,17 +219,15 @@ import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
import com.android.systemui.statusbar.phone.ScrimController;
+import com.android.systemui.statusbar.phone.ShadeTouchableRegionManager;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager;
import com.android.systemui.statusbar.phone.TapAgainViewController;
import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.KeyguardUserSwitcherController;
import com.android.systemui.statusbar.policy.KeyguardUserSwitcherView;
-import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
import com.android.systemui.statusbar.policy.SplitShadeStateController;
import com.android.systemui.unfold.SysUIUnfoldComponent;
import com.android.systemui.util.Compile;
@@ -298,7 +298,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
* The minimum scale to "squish" the Shade and associated elements down to, for Back gesture
*/
public static final float SHADE_BACK_ANIM_MIN_SCALE = 0.9f;
- private final StatusBarTouchableRegionManager mStatusBarTouchableRegionManager;
+ private final ShadeTouchableRegionManager mShadeTouchableRegionManager;
private final Resources mResources;
private final KeyguardStateController mKeyguardStateController;
private final SysuiStatusBarStateController mStatusBarStateController;
@@ -695,7 +695,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
ShadeLogger shadeLogger,
@ShadeDisplayAware ConfigurationController configurationController,
Provider<FlingAnimationUtils.Builder> flingAnimationUtilsBuilder,
- StatusBarTouchableRegionManager statusBarTouchableRegionManager,
+ ShadeTouchableRegionManager shadeTouchableRegionManager,
ConversationNotificationManager conversationNotificationManager,
MediaHierarchyManager mediaHierarchyManager,
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
@@ -840,7 +840,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
mVibratorHelper = vibratorHelper;
mMSDLPlayer = msdlPlayer;
mVibrateOnOpening = mResources.getBoolean(R.bool.config_vibrateOnIconAnimation);
- mStatusBarTouchableRegionManager = statusBarTouchableRegionManager;
+ mShadeTouchableRegionManager = shadeTouchableRegionManager;
mSystemClock = systemClock;
mKeyguardMediaController = keyguardMediaController;
mMetricsLogger = metricsLogger;
@@ -1551,7 +1551,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
private Rect calculateGestureExclusionRect() {
Rect exclusionRect = null;
- Region touchableRegion = mStatusBarTouchableRegionManager.calculateTouchableRegion();
+ Region touchableRegion = mShadeTouchableRegionManager.calculateTouchableRegion();
if (isFullyCollapsed() && touchableRegion != null) {
// Note: The manager also calculates the non-pinned touchable region
exclusionRect = touchableRegion.getBounds();
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
index 04f89be97e0a..0df2299eb8dd 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
@@ -98,7 +98,7 @@ import com.android.systemui.statusbar.phone.LightBarController;
import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager;
+import com.android.systemui.statusbar.phone.ShadeTouchableRegionManager;
import com.android.systemui.statusbar.policy.CastController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.SplitShadeStateController;
@@ -141,7 +141,7 @@ public class QuickSettingsControllerImpl implements QuickSettingsController, Dum
private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
private final NotificationShadeDepthController mDepthController;
private final ShadeHeaderController mShadeHeaderController;
- private final StatusBarTouchableRegionManager mStatusBarTouchableRegionManager;
+ private final ShadeTouchableRegionManager mShadeTouchableRegionManager;
private final Provider<StatusBarLongPressGestureDetector> mStatusBarLongPressGestureDetector;
private final KeyguardStateController mKeyguardStateController;
private final KeyguardBypassController mKeyguardBypassController;
@@ -317,7 +317,7 @@ public class QuickSettingsControllerImpl implements QuickSettingsController, Dum
LockscreenShadeTransitionController lockscreenShadeTransitionController,
NotificationShadeDepthController notificationShadeDepthController,
ShadeHeaderController shadeHeaderController,
- StatusBarTouchableRegionManager statusBarTouchableRegionManager,
+ ShadeTouchableRegionManager shadeTouchableRegionManager,
Provider<StatusBarLongPressGestureDetector> statusBarLongPressGestureDetector,
KeyguardStateController keyguardStateController,
KeyguardBypassController keyguardBypassController,
@@ -366,7 +366,7 @@ public class QuickSettingsControllerImpl implements QuickSettingsController, Dum
mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
mDepthController = notificationShadeDepthController;
mShadeHeaderController = shadeHeaderController;
- mStatusBarTouchableRegionManager = statusBarTouchableRegionManager;
+ mShadeTouchableRegionManager = shadeTouchableRegionManager;
mStatusBarLongPressGestureDetector = statusBarLongPressGestureDetector;
mKeyguardStateController = keyguardStateController;
mKeyguardBypassController = keyguardBypassController;
@@ -695,7 +695,7 @@ public class QuickSettingsControllerImpl implements QuickSettingsController, Dum
/* right= */ (int) mQsFrame.getX() + mQsFrame.getWidth(),
/* bottom= */ headerBottom + frameTop);
// Also allow QS to intercept if the touch is near the notch.
- mStatusBarTouchableRegionManager.updateRegionForNotch(mInterceptRegion);
+ mShadeTouchableRegionManager.updateRegionForNotch(mInterceptRegion);
final boolean onHeader = mInterceptRegion.contains((int) x, (int) y);
if (getExpanded()) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
index 0b36e685d914..91ca2ca52287 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
@@ -17,9 +17,10 @@
package com.android.systemui.shade
import android.content.Context
-import android.content.MutableContextWrapper
import android.content.res.Resources
import android.view.LayoutInflater
+import android.view.WindowManager
+import android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE
import com.android.systemui.CoreStartable
import com.android.systemui.common.ui.ConfigurationState
import com.android.systemui.common.ui.ConfigurationStateImpl
@@ -29,9 +30,12 @@ import com.android.systemui.common.ui.data.repository.ConfigurationRepositoryImp
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractorImpl
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.res.R
import com.android.systemui.scene.ui.view.WindowRootView
+import com.android.systemui.shade.data.repository.MutableShadeDisplaysRepository
import com.android.systemui.shade.data.repository.ShadeDisplaysRepository
import com.android.systemui.shade.data.repository.ShadeDisplaysRepositoryImpl
+import com.android.systemui.shade.display.ShadeDisplayPolicyModule
import com.android.systemui.shade.domain.interactor.ShadeDisplaysInteractor
import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
import com.android.systemui.statusbar.phone.ConfigurationControllerImpl
@@ -56,7 +60,7 @@ import javax.inject.Provider
* By using this dedicated module, we ensure the notification shade window always utilizes the
* correct display context and resources, regardless of the display it's on.
*/
-@Module(includes = [OptionalShadeDisplayAwareBindings::class])
+@Module(includes = [OptionalShadeDisplayAwareBindings::class, ShadeDisplayPolicyModule::class])
object ShadeDisplayAwareModule {
/** Creates a new context for the shade window. */
@@ -65,7 +69,9 @@ object ShadeDisplayAwareModule {
@SysUISingleton
fun provideShadeDisplayAwareContext(context: Context): Context {
return if (ShadeWindowGoesAround.isEnabled) {
- MutableContextWrapper(context)
+ context
+ .createWindowContext(context.display, TYPE_NOTIFICATION_SHADE, /* options= */ null)
+ .apply { setTheme(R.style.Theme_SystemUI) }
} else {
context
}
@@ -74,6 +80,20 @@ object ShadeDisplayAwareModule {
@Provides
@ShadeDisplayAware
@SysUISingleton
+ fun provideShadeWindowManager(
+ defaultWindowManager: WindowManager,
+ @ShadeDisplayAware context: Context,
+ ): WindowManager {
+ return if (ShadeWindowGoesAround.isEnabled) {
+ context.getSystemService(WindowManager::class.java) as WindowManager
+ } else {
+ defaultWindowManager
+ }
+ }
+
+ @Provides
+ @ShadeDisplayAware
+ @SysUISingleton
fun provideShadeDisplayAwareResources(@ShadeDisplayAware context: Context): Resources {
return context.resources
}
@@ -163,6 +183,15 @@ object ShadeDisplayAwareModule {
return impl
}
+ @SysUISingleton
+ @Provides
+ fun provideMutableShadePositionRepository(
+ impl: ShadeDisplaysRepositoryImpl
+ ): MutableShadeDisplaysRepository {
+ ShadeWindowGoesAround.isUnexpectedlyInLegacyMode()
+ return impl
+ }
+
@Provides
@IntoMap
@ClassKey(ShadePrimaryDisplayCommand::class)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadePrimaryDisplayCommand.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadePrimaryDisplayCommand.kt
index a5d9e9660ca5..a54f6b9c6743 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadePrimaryDisplayCommand.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadePrimaryDisplayCommand.kt
@@ -20,11 +20,14 @@ import android.view.Display
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.display.data.repository.DisplayRepository
-import com.android.systemui.shade.data.repository.ShadeDisplaysRepository
+import com.android.systemui.shade.data.repository.MutableShadeDisplaysRepository
+import com.android.systemui.shade.display.ShadeDisplayPolicy
+import com.android.systemui.shade.display.SpecificDisplayIdPolicy
import com.android.systemui.statusbar.commandline.Command
import com.android.systemui.statusbar.commandline.CommandRegistry
import java.io.PrintWriter
import javax.inject.Inject
+import kotlin.text.toIntOrNull
@SysUISingleton
class ShadePrimaryDisplayCommand
@@ -32,7 +35,9 @@ class ShadePrimaryDisplayCommand
constructor(
private val commandRegistry: CommandRegistry,
private val displaysRepository: DisplayRepository,
- private val positionRepository: ShadeDisplaysRepository,
+ private val positionRepository: MutableShadeDisplaysRepository,
+ private val policies: Set<@JvmSuppressWildcards ShadeDisplayPolicy>,
+ private val defaultPolicy: ShadeDisplayPolicy,
) : Command, CoreStartable {
override fun start() {
@@ -40,8 +45,11 @@ constructor(
}
override fun help(pw: PrintWriter) {
- pw.println("shade_display_override <displayId> ")
- pw.println("Set the display which is holding the shade.")
+ pw.println("shade_display_override (<displayId>|<policyName>) ")
+ pw.println("Set the display which is holding the shade, or the policy that defines it.")
+ pw.println()
+ pw.println("shade_display_override policies")
+ pw.println("Lists available policies")
pw.println()
pw.println("shade_display_override reset ")
pw.println("Reset the display which is holding the shade.")
@@ -68,21 +76,27 @@ constructor(
"reset" -> reset()
"list",
"status" -> printStatus()
+ "policies" -> printPolicies()
"any_external" -> anyExternal()
- else -> {
- val cmdAsInteger = command?.toIntOrNull()
- if (cmdAsInteger != null) {
- changeDisplay(displayId = cmdAsInteger)
- } else {
- help(pw)
- }
+ null -> help(pw)
+ else -> parsePolicy(command)
+ }
+ }
+
+ private fun parsePolicy(policyIdentifier: String) {
+ val displayId = policyIdentifier.toIntOrNull()
+ when {
+ displayId != null -> changeDisplay(displayId = displayId)
+ policies.any { it.name == policyIdentifier } -> {
+ positionRepository.policy.value = policies.first { it.name == policyIdentifier }
}
+ else -> help(pw)
}
}
private fun reset() {
- positionRepository.resetDisplayId()
- pw.println("Reset shade primary display id to ${Display.DEFAULT_DISPLAY}")
+ positionRepository.policy.value = defaultPolicy
+ pw.println("Reset shade display policy to default policy: ${defaultPolicy.name}")
}
private fun printStatus() {
@@ -95,6 +109,15 @@ constructor(
}
}
+ private fun printPolicies() {
+ val currentPolicyName = positionRepository.policy.value.name
+ pw.println("Available policies: ")
+ policies.forEach {
+ pw.print(" - ${it.name}")
+ pw.println(if (currentPolicyName == it.name) " (Current policy)" else "")
+ }
+ }
+
private fun anyExternal() {
val anyExternalDisplay =
displaysRepository.displays.value.firstOrNull {
@@ -116,7 +139,7 @@ constructor(
}
private fun setDisplay(id: Int) {
- positionRepository.setDisplayId(id)
+ positionRepository.policy.value = SpecificDisplayIdPolicy(id)
pw.println("New shade primary display id is $id")
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
index 7346a28dc7a7..9ca23f0f1e17 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurface.kt
@@ -19,8 +19,8 @@ import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
import com.android.systemui.shade.domain.interactor.ShadeBackActionInteractor
import com.android.systemui.shade.domain.interactor.ShadeLockscreenInteractor
import com.android.systemui.statusbar.GestureRecorder
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
import com.android.systemui.statusbar.phone.CentralSurfaces
-import com.android.systemui.statusbar.policy.HeadsUpManager
/**
* Allows CentralSurfacesImpl to interact with the shade. Only CentralSurfacesImpl should reference
@@ -37,7 +37,7 @@ interface ShadeSurface :
centralSurfaces: CentralSurfaces,
recorder: GestureRecorder,
hideExpandedRunnable: Runnable,
- headsUpManager: HeadsUpManager
+ headsUpManager: HeadsUpManager,
)
/** Cancels any pending collapses. */
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeSurfaceImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurfaceImpl.kt
index ec4018c7d238..3bbda162cf31 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeSurfaceImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurfaceImpl.kt
@@ -17,8 +17,8 @@
package com.android.systemui.shade
import com.android.systemui.statusbar.GestureRecorder
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
import com.android.systemui.statusbar.phone.CentralSurfaces
-import com.android.systemui.statusbar.policy.HeadsUpManager
import javax.inject.Inject
class ShadeSurfaceImpl @Inject constructor() : ShadeSurface, ShadeViewControllerEmptyImpl() {
@@ -26,7 +26,7 @@ class ShadeSurfaceImpl @Inject constructor() : ShadeSurface, ShadeViewController
centralSurfaces: CentralSurfaces,
recorder: GestureRecorder,
hideExpandedRunnable: Runnable,
- headsUpManager: HeadsUpManager
+ headsUpManager: HeadsUpManager,
) {}
override fun cancelPendingCollapse() {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/FakeShadeDisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/FakeShadeDisplayRepository.kt
index 71c565816362..732d4d1500e7 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/FakeShadeDisplayRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/FakeShadeDisplayRepository.kt
@@ -23,14 +23,14 @@ import kotlinx.coroutines.flow.StateFlow
class FakeShadeDisplayRepository : ShadeDisplaysRepository {
private val _displayId = MutableStateFlow(Display.DEFAULT_DISPLAY)
- override fun setDisplayId(displayId: Int) {
+ fun setDisplayId(displayId: Int) {
_displayId.value = displayId
}
override val displayId: StateFlow<Int>
get() = _displayId
- override fun resetDisplayId() {
+ fun resetDisplayId() {
_displayId.value = Display.DEFAULT_DISPLAY
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepository.kt
index 4a95e339cade..756241e9b071 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepository.kt
@@ -18,37 +18,40 @@ package com.android.systemui.shade.data.repository
import android.view.Display
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.shade.display.ShadeDisplayPolicy
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.stateIn
+/** Source of truth for the display currently holding the shade. */
interface ShadeDisplaysRepository {
/** ID of the display which currently hosts the shade */
val displayId: StateFlow<Int>
+}
- /**
- * Updates the value of the shade display id stored, emitting to the new display id to every
- * component dependent on the shade display id
- */
- fun setDisplayId(displayId: Int)
-
- /** Resets value of shade primary display to the default display */
- fun resetDisplayId()
+/** Allows to change the policy that determines in which display the Shade window is visible. */
+interface MutableShadeDisplaysRepository : ShadeDisplaysRepository {
+ /** Updates the policy to select where the shade is visible. */
+ val policy: MutableStateFlow<ShadeDisplayPolicy>
}
-/** Source of truth for the display currently holding the shade. */
+/** Keeps the policy and propagates the display id for the shade from it. */
@SysUISingleton
-class ShadeDisplaysRepositoryImpl @Inject constructor() : ShadeDisplaysRepository {
- private val _displayId = MutableStateFlow(Display.DEFAULT_DISPLAY)
-
- override val displayId: StateFlow<Int>
- get() = _displayId
-
- override fun setDisplayId(displayId: Int) {
- _displayId.value = displayId
- }
-
- override fun resetDisplayId() {
- _displayId.value = Display.DEFAULT_DISPLAY
- }
+@OptIn(ExperimentalCoroutinesApi::class)
+class ShadeDisplaysRepositoryImpl
+@Inject
+constructor(defaultPolicy: ShadeDisplayPolicy, @Background bgScope: CoroutineScope) :
+ MutableShadeDisplaysRepository {
+ override val policy = MutableStateFlow<ShadeDisplayPolicy>(defaultPolicy)
+
+ override val displayId: StateFlow<Int> =
+ policy
+ .flatMapLatest { it.displayId }
+ .stateIn(bgScope, SharingStarted.WhileSubscribed(), Display.DEFAULT_DISPLAY)
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/display/AnyExternalShadeDisplayPolicy.kt b/packages/SystemUI/src/com/android/systemui/shade/display/AnyExternalShadeDisplayPolicy.kt
new file mode 100644
index 000000000000..3f6c949fcba6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/display/AnyExternalShadeDisplayPolicy.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.display
+
+import android.view.Display
+import android.view.Display.DEFAULT_DISPLAY
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.display.data.repository.DisplayRepository
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * Returns an external display if one exists, otherwise the default display.
+ *
+ * If there are multiple external displays, the one with minimum display ID is returned.
+ */
+@SysUISingleton
+class AnyExternalShadeDisplayPolicy
+@Inject
+constructor(displayRepository: DisplayRepository, @Background bgScope: CoroutineScope) :
+ ShadeDisplayPolicy {
+ override val name: String
+ get() = "any_external_display"
+
+ override val displayId: StateFlow<Int> =
+ displayRepository.displays
+ .map { displays ->
+ displays
+ .filter { it.displayId != DEFAULT_DISPLAY && it.type in ALLOWED_DISPLAY_TYPES }
+ .minOfOrNull { it.displayId } ?: DEFAULT_DISPLAY
+ }
+ .stateIn(bgScope, SharingStarted.WhileSubscribed(), DEFAULT_DISPLAY)
+
+ private companion object {
+ val ALLOWED_DISPLAY_TYPES =
+ setOf(Display.TYPE_EXTERNAL, Display.TYPE_OVERLAY, Display.TYPE_WIFI)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/display/ShadeDisplayPolicy.kt b/packages/SystemUI/src/com/android/systemui/shade/display/ShadeDisplayPolicy.kt
new file mode 100644
index 000000000000..1b22ee40009b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/display/ShadeDisplayPolicy.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.display
+
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoSet
+import kotlinx.coroutines.flow.StateFlow
+
+/** Describes the display the shade should be shown in. */
+interface ShadeDisplayPolicy {
+ val name: String
+
+ /** The display id the shade should be at, according to this policy. */
+ val displayId: StateFlow<Int>
+}
+
+@Module
+interface ShadeDisplayPolicyModule {
+ @IntoSet
+ @Binds
+ fun provideDefaultPolicyToSet(impl: DefaultShadeDisplayPolicy): ShadeDisplayPolicy
+
+ @IntoSet
+ @Binds
+ fun provideAnyExternalShadeDisplayPolicyToSet(
+ impl: AnyExternalShadeDisplayPolicy
+ ): ShadeDisplayPolicy
+
+ @Binds fun provideDefaultPolicy(impl: DefaultShadeDisplayPolicy): ShadeDisplayPolicy
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/display/SpecificDisplayIdPolicy.kt b/packages/SystemUI/src/com/android/systemui/shade/display/SpecificDisplayIdPolicy.kt
new file mode 100644
index 000000000000..13e766409bab
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/display/SpecificDisplayIdPolicy.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.display
+
+import android.view.Display
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+
+/** Policy to specify a display id explicitly. */
+open class SpecificDisplayIdPolicy(displayId: Int) : ShadeDisplayPolicy {
+ override val name: String
+ get() = "display_${displayId}_policy"
+
+ override val displayId: StateFlow<Int> = MutableStateFlow(displayId)
+}
+
+class DefaultShadeDisplayPolicy @Inject constructor() :
+ SpecificDisplayIdPolicy(Display.DEFAULT_DISPLAY)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt
index 432d5f553fbb..fb2cbec84236 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt
@@ -16,28 +16,21 @@
package com.android.systemui.shade.domain.interactor
-import android.content.ComponentCallbacks
import android.content.Context
-import android.content.MutableContextWrapper
-import android.content.res.Configuration
-import android.content.res.Resources
import android.util.Log
import android.view.WindowManager
-import android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE
+import androidx.annotation.UiThread
import com.android.app.tracing.coroutines.launchTraced
import com.android.app.tracing.traceSection
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository
-import com.android.systemui.display.shared.model.DisplayWindowProperties
import com.android.systemui.scene.ui.view.WindowRootView
import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.shade.ShadeWindowLayoutParams
import com.android.systemui.shade.data.repository.ShadeDisplaysRepository
import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
-import com.android.systemui.statusbar.phone.ConfigurationForwarder
import com.android.systemui.util.kotlin.getOrNull
import java.util.Optional
import javax.inject.Inject
@@ -53,13 +46,14 @@ constructor(
optionalShadeRootView: Optional<WindowRootView>,
private val shadePositionRepository: ShadeDisplaysRepository,
@ShadeDisplayAware private val shadeContext: Context,
- @ShadeDisplayAware private val shadeResources: Resources,
- private val displayWindowPropertiesRepository: DisplayWindowPropertiesRepository,
+ @ShadeDisplayAware private val wm: WindowManager,
@Background private val bgScope: CoroutineScope,
- @ShadeDisplayAware private val shadeConfigurationForwarder: ConfigurationForwarder,
@Main private val mainThreadContext: CoroutineContext,
) : CoreStartable {
+ private val shadeLayoutParams: WindowManager.LayoutParams =
+ ShadeWindowLayoutParams.create(shadeContext)
+
private val shadeRootView =
optionalShadeRootView.getOrNull()
?: error(
@@ -69,9 +63,6 @@ constructor(
"""
.trimIndent()
)
- // TODO: b/362719719 - Get rid of this callback as the root view should automatically get the
- // correct configuration once it's moved to another window.
- private var unregisterConfigChangedCallbacks: (() -> Unit)? = null
override fun start() {
ShadeWindowGoesAround.isUnexpectedlyInLegacyMode()
@@ -94,7 +85,7 @@ constructor(
return
}
try {
- moveShadeWindow(fromId = currentId, toId = destinationId)
+ withContext(mainThreadContext) { moveShadeWindow(toId = destinationId) }
} catch (e: IllegalStateException) {
Log.e(
TAG,
@@ -104,68 +95,26 @@ constructor(
}
}
- private suspend fun moveShadeWindow(fromId: Int, toId: Int) {
- val (_, _, _, sourceWm) = getDisplayWindowProperties(fromId)
- val (_, _, destContext, destWm) = getDisplayWindowProperties(toId)
- withContext(mainThreadContext) {
- traceSection({ "MovingShadeWindow from $fromId to $toId" }) {
- removeShade(sourceWm)
- addShade(destWm)
- overrideContextAndResources(newContext = destContext)
- registerConfigurationChange(destContext)
- }
- traceSection("ShadeDisplaysInteractor#onConfigurationChanged") {
- dispatchConfigurationChanged(destContext.resources.configuration)
- }
- }
- }
-
- private fun removeShade(wm: WindowManager): Unit =
- traceSection("removeView") { wm.removeView(shadeRootView) }
-
- private fun addShade(wm: WindowManager): Unit =
- traceSection("addView") {
- wm.addView(shadeRootView, ShadeWindowLayoutParams.create(shadeContext))
+ @UiThread
+ private fun moveShadeWindow(toId: Int) {
+ traceSection({ "moveShadeWindow to $toId" }) {
+ removeShadeWindow()
+ updateContextDisplay(toId)
+ addShadeWindow()
}
-
- private fun overrideContextAndResources(newContext: Context) {
- val contextWrapper =
- shadeContext as? MutableContextWrapper
- ?: error("Shade context is not a MutableContextWrapper!")
- contextWrapper.baseContext = newContext
- // Override needed in case someone is keeping a reference to the resources from the old
- // context.
- // TODO: b/362719719 - This shouldn't be needed, as resources should be updated when the
- // window is moved to the new display automatically.
- shadeResources.impl = shadeContext.resources.impl
- }
-
- private fun dispatchConfigurationChanged(newConfig: Configuration) {
- shadeConfigurationForwarder.onConfigurationChanged(newConfig)
- shadeRootView.dispatchConfigurationChanged(newConfig)
- shadeRootView.requestLayout()
}
- private fun registerConfigurationChange(context: Context) {
- // we should keep only one at the time.
- unregisterConfigChangedCallbacks?.invoke()
- val callback =
- object : ComponentCallbacks {
- override fun onConfigurationChanged(newConfig: Configuration) {
- dispatchConfigurationChanged(newConfig)
- }
+ @UiThread
+ private fun removeShadeWindow(): Unit =
+ traceSection("removeShadeWindow") { wm.removeView(shadeRootView) }
- override fun onLowMemory() {}
- }
- context.registerComponentCallbacks(callback)
- unregisterConfigChangedCallbacks = {
- context.unregisterComponentCallbacks(callback)
- unregisterConfigChangedCallbacks = null
- }
- }
+ @UiThread
+ private fun addShadeWindow(): Unit =
+ traceSection("addShadeWindow") { wm.addView(shadeRootView, shadeLayoutParams) }
- private fun getDisplayWindowProperties(displayId: Int): DisplayWindowProperties {
- return displayWindowPropertiesRepository.get(displayId, TYPE_NOTIFICATION_SHADE)
+ @UiThread
+ private fun updateContextDisplay(newDisplayId: Int) {
+ traceSection("updateContextDisplay") { shadeContext.updateDisplay(newDisplayId) }
}
private companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModel.kt
index ce4c081358ba..0253122183eb 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModel.kt
@@ -30,15 +30,21 @@ import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.settings.brightness.ui.viewModel.BrightnessMirrorViewModel
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.shared.model.ShadeMode
+import com.android.systemui.statusbar.disableflags.domain.interactor.DisableFlagsInteractor
import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import java.util.concurrent.atomic.AtomicBoolean
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
/**
* Models UI state used to render the content of the shade scene.
@@ -54,6 +60,7 @@ constructor(
val brightnessMirrorViewModelFactory: BrightnessMirrorViewModel.Factory,
val mediaCarouselInteractor: MediaCarouselInteractor,
shadeInteractor: ShadeInteractor,
+ private val disableFlagsInteractor: DisableFlagsInteractor,
private val footerActionsViewModelFactory: FooterActionsViewModel.Factory,
private val footerActionsController: FooterActionsController,
private val unfoldTransitionInteractor: UnfoldTransitionInteractor,
@@ -70,12 +77,21 @@ constructor(
val isMediaVisible: StateFlow<Boolean> = mediaCarouselInteractor.hasActiveMediaOrRecommendation
+ private val _isQsEnabled =
+ MutableStateFlow(!disableFlagsInteractor.disableFlags.value.isQuickSettingsEnabled())
+ val isQsEnabled: StateFlow<Boolean> = _isQsEnabled.asStateFlow()
+
private val footerActionsControllerInitialized = AtomicBoolean(false)
- override suspend fun onActivated(): Nothing {
- deviceEntryInteractor.isDeviceEntered.collect { isDeviceEntered ->
- _isEmptySpaceClickable.value = !isDeviceEntered
- }
+ override suspend fun onActivated(): Nothing = coroutineScope {
+ deviceEntryInteractor.isDeviceEntered
+ .onEach { isDeviceEntered -> _isEmptySpaceClickable.value = !isDeviceEntered }
+ .launchIn(this)
+ disableFlagsInteractor.disableFlags
+ .map { it.isQuickSettingsEnabled() }
+ .onEach { _isQsEnabled.value = it }
+ .launchIn(this)
+ awaitCancellation()
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
index e19fcd05e9a4..85b8bf9aec80 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
@@ -39,24 +39,24 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.statusbar.policy.HeadsUpManager
import java.io.PrintWriter
import javax.inject.Inject
import kotlin.math.max
/**
* A utility class that handles notification panel expansion when a user swipes downward on a
- * notification from the pulsing state.
- * If face-bypass is enabled, the user can swipe down anywhere on the screen (not just from a
- * notification) to trigger the notification panel expansion.
+ * notification from the pulsing state. If face-bypass is enabled, the user can swipe down anywhere
+ * on the screen (not just from a notification) to trigger the notification panel expansion.
*/
@SysUISingleton
-class PulseExpansionHandler @Inject
+class PulseExpansionHandler
+@Inject
constructor(
context: Context,
private val wakeUpCoordinator: NotificationWakeUpCoordinator,
@@ -67,7 +67,7 @@ constructor(
private val falsingManager: FalsingManager,
private val shadeInteractor: ShadeInteractor,
private val lockscreenShadeTransitionController: LockscreenShadeTransitionController,
- dumpManager: DumpManager
+ dumpManager: DumpManager,
) : Gefingerpoken, Dumpable {
companion object {
private val SPRING_BACK_ANIMATION_LENGTH_MS = 375
@@ -91,14 +91,13 @@ constructor(
pulseExpandAbortListener?.run()
}
}
- headsUpManager.unpinAll(
- /*userUnPinned= */
- true,
- )
+ headsUpManager.unpinAll(/* userUnPinned= */ true)
}
}
+
var leavingLockscreen: Boolean = false
private set
+
private var touchSlop = 0f
private var minDragDistance = 0
private lateinit var stackScrollerController: NotificationStackScrollLayoutController
@@ -111,24 +110,27 @@ constructor(
private val isFalseTouch: Boolean
get() = falsingManager.isFalseTouch(NOTIFICATION_DRAG_DOWN)
+
var pulseExpandAbortListener: Runnable? = null
var bouncerShowing: Boolean = false
init {
initResources(context)
- configurationController.addCallback(object : ConfigurationController.ConfigurationListener {
- override fun onConfigChanged(newConfig: Configuration?) {
- initResources(context)
+ configurationController.addCallback(
+ object : ConfigurationController.ConfigurationListener {
+ override fun onConfigChanged(newConfig: Configuration?) {
+ initResources(context)
+ }
}
- })
+ )
mPowerManager = context.getSystemService(PowerManager::class.java)
dumpManager.registerDumpable(this)
}
private fun initResources(context: Context) {
- minDragDistance = context.resources.getDimensionPixelSize(
- R.dimen.keyguard_drag_down_min_distance)
+ minDragDistance =
+ context.resources.getDimensionPixelSize(R.dimen.keyguard_drag_down_min_distance)
touchSlop = ViewConfiguration.get(context).scaledTouchSlop.toFloat()
}
@@ -137,7 +139,8 @@ constructor(
}
private fun canHandleMotionEvent(): Boolean {
- return wakeUpCoordinator.canShowPulsingHuns && !shadeInteractor.isQsExpanded.value &&
+ return wakeUpCoordinator.canShowPulsingHuns &&
+ !shadeInteractor.isQsExpanded.value &&
!bouncerShowing
}
@@ -189,18 +192,20 @@ constructor(
}
override fun onTouchEvent(event: MotionEvent): Boolean {
- val finishExpanding = (event.action == MotionEvent.ACTION_CANCEL ||
- event.action == MotionEvent.ACTION_UP) && isExpanding
+ val finishExpanding =
+ (event.action == MotionEvent.ACTION_CANCEL || event.action == MotionEvent.ACTION_UP) &&
+ isExpanding
- val isDraggingNotificationOrCanBypass = mStartingChild?.showingPulsing() == true ||
- bypassController.canBypass()
+ val isDraggingNotificationOrCanBypass =
+ mStartingChild?.showingPulsing() == true || bypassController.canBypass()
if ((!canHandleMotionEvent() || !isDraggingNotificationOrCanBypass) && !finishExpanding) {
// We allow cancellations/finishing to still go through here to clean up the state
return false
}
- if (velocityTracker == null || !isExpanding ||
- event.actionMasked == MotionEvent.ACTION_DOWN) {
+ if (
+ velocityTracker == null || !isExpanding || event.actionMasked == MotionEvent.ACTION_DOWN
+ ) {
return startExpansion(event)
}
velocityTracker!!.addMovement(event)
@@ -210,12 +215,11 @@ constructor(
when (event.actionMasked) {
MotionEvent.ACTION_MOVE -> updateExpansionHeight(moveDistance)
MotionEvent.ACTION_UP -> {
- velocityTracker!!.computeCurrentVelocity(
- /* units= */
- 1000,
- )
- val canExpand = moveDistance > 0 && velocityTracker!!.getYVelocity() > -1000 &&
- statusBarStateController.state != StatusBarState.SHADE
+ velocityTracker!!.computeCurrentVelocity(/* units= */ 1000)
+ val canExpand =
+ moveDistance > 0 &&
+ velocityTracker!!.getYVelocity() > -1000 &&
+ statusBarStateController.state != StatusBarState.SHADE
if (!falsingManager.isUnlockingDisabled && !isFalseTouch && canExpand) {
finishExpansion()
} else {
@@ -243,22 +247,16 @@ constructor(
mPowerManager!!.wakeUp(
SystemClock.uptimeMillis(),
PowerManager.WAKE_REASON_GESTURE,
- "com.android.systemui:PULSEDRAG"
+ "com.android.systemui:PULSEDRAG",
)
}
- lockscreenShadeTransitionController.goToLockedShade(
- startingChild,
- needsQSAnimation = false
- )
+ lockscreenShadeTransitionController.goToLockedShade(startingChild, needsQSAnimation = false)
lockscreenShadeTransitionController.finishPulseAnimation(cancelled = false)
leavingLockscreen = true
isExpanding = false
if (mStartingChild is ExpandableNotificationRow) {
val row = mStartingChild as ExpandableNotificationRow?
- row!!.onExpandedByGesture(
- /*userExpanded= */
- true,
- )
+ row!!.onExpandedByGesture(/* userExpanded= */ true)
}
}
@@ -266,19 +264,15 @@ constructor(
var expansionHeight = max(height, 0.0f)
if (mStartingChild != null) {
val child = mStartingChild!!
- val newHeight = Math.min(
- (child.collapsedHeight + expansionHeight).toInt(),
- child.maxContentHeight
- )
+ val newHeight =
+ Math.min((child.collapsedHeight + expansionHeight).toInt(), child.maxContentHeight)
child.actualHeight = newHeight
} else {
wakeUpCoordinator.setNotificationsVisibleForExpansion(
- height
- > lockscreenShadeTransitionController.distanceUntilShowingPulsingNotifications,
- /*animate= */
- true,
- /*increaseSpeed= */
- true
+ height >
+ lockscreenShadeTransitionController.distanceUntilShowingPulsingNotifications,
+ /*animate= */ true,
+ /*increaseSpeed= */ true,
)
}
lockscreenShadeTransitionController.setPulseHeight(expansionHeight, animate = false)
@@ -296,7 +290,7 @@ constructor(
@VisibleForTesting
fun reset(
child: ExpandableView,
- animationDuration: Long = SPRING_BACK_ANIMATION_LENGTH_MS.toLong()
+ animationDuration: Long = SPRING_BACK_ANIMATION_LENGTH_MS.toLong(),
) {
if (child.actualHeight == child.collapsedHeight) {
setUserLocked(child, false)
@@ -309,11 +303,13 @@ constructor(
// don't use reflection, because the `actualHeight` field may be obfuscated
child.actualHeight = animation.animatedValue as Int
}
- anim.addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator) {
- setUserLocked(child, false)
+ anim.addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ setUserLocked(child, false)
+ }
}
- })
+ )
anim.start()
}
@@ -331,12 +327,9 @@ constructor(
}
lockscreenShadeTransitionController.finishPulseAnimation(cancelled = true)
wakeUpCoordinator.setNotificationsVisibleForExpansion(
- /*visible= */
- false,
- /*animate= */
- true,
- /*increaseSpeed= */
- false
+ /*visible= */ false,
+ /*animate= */ true,
+ /*increaseSpeed= */ false,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/StatusBarChipsModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/StatusBarChipsModule.kt
index 8ce0dbf8e171..6db610bbc3a6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/StatusBarChipsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/StatusBarChipsModule.kt
@@ -21,7 +21,10 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogBufferFactory
import com.android.systemui.statusbar.chips.notification.demo.ui.viewmodel.DemoNotifChipViewModel
+import com.android.systemui.statusbar.chips.notification.domain.interactor.StatusBarNotificationChipsInteractor
+import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import dagger.Binds
+import dagger.Lazy
import dagger.Module
import dagger.Provides
import dagger.multibindings.ClassKey
@@ -41,5 +44,19 @@ abstract class StatusBarChipsModule {
fun provideChipsLogBuffer(factory: LogBufferFactory): LogBuffer {
return factory.create("StatusBarChips", 200)
}
+
+ @Provides
+ @SysUISingleton
+ @IntoMap
+ @ClassKey(StatusBarNotificationChipsInteractor::class)
+ fun statusBarNotificationChipsInteractorAsCoreStartable(
+ interactorLazy: Lazy<StatusBarNotificationChipsInteractor>
+ ): CoreStartable {
+ return if (StatusBarNotifChips.isEnabled) {
+ interactorLazy.get()
+ } else {
+ CoreStartable.NOP
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt
new file mode 100644
index 000000000000..087b51032fcf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.chips.notification.domain.interactor
+
+import com.android.systemui.activity.data.repository.ActivityManagerRepository
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.Logger
+import com.android.systemui.statusbar.chips.StatusBarChipLogTags.pad
+import com.android.systemui.statusbar.chips.StatusBarChipsLog
+import com.android.systemui.statusbar.chips.notification.domain.model.NotificationChipModel
+import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
+
+/**
+ * Interactor representing a single notification's status bar chip.
+ *
+ * [startingModel.key] dictates which notification this interactor corresponds to - all updates sent
+ * to this interactor via [setNotification] should only be for the notification with the same key.
+ *
+ * [StatusBarNotificationChipsInteractor] will collect all the individual instances of this
+ * interactor and send all the necessary information to the UI layer.
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+class SingleNotificationChipInteractor
+@AssistedInject
+constructor(
+ @Assisted startingModel: ActiveNotificationModel,
+ private val activityManagerRepository: ActivityManagerRepository,
+ @StatusBarChipsLog private val logBuffer: LogBuffer,
+) {
+ private val key = startingModel.key
+ private val logger = Logger(logBuffer, "Notif".pad())
+ // [StatusBarChipLogTag] recommends a max tag length of 20, so [extraLogTag] should NOT be the
+ // top-level tag. It should instead be provided as the first string in each log message.
+ private val extraLogTag = "SingleChipInteractor[key=$key]"
+
+ private val _notificationModel = MutableStateFlow(startingModel)
+
+ /**
+ * Sets the new notification info corresponding to this interactor. The key on [model] *must*
+ * match the key on the original [startingModel], otherwise the update won't be processed.
+ */
+ fun setNotification(model: ActiveNotificationModel) {
+ if (model.key != this.key) {
+ logger.w({ "$str1: received model for different key $str2" }) {
+ str1 = extraLogTag
+ str2 = model.key
+ }
+ return
+ }
+ _notificationModel.value = model
+ }
+
+ private val uid: Flow<Int> = _notificationModel.map { it.uid }
+
+ /** True if the application managing the notification is visible to the user. */
+ private val isAppVisible: Flow<Boolean> =
+ uid.flatMapLatest { currentUid ->
+ activityManagerRepository.createIsAppVisibleFlow(currentUid, logger, extraLogTag)
+ }
+
+ /**
+ * Emits this notification's status bar chip, or null if this notification shouldn't show a
+ * status bar chip.
+ */
+ val notificationChip: Flow<NotificationChipModel?> =
+ combine(_notificationModel, isAppVisible) { notif, isAppVisible ->
+ if (isAppVisible) {
+ // If the app that posted this notification is visible, we want to hide the chip
+ // because information between the status bar chip and the app itself could be
+ // out-of-sync (like a timer that's slightly off)
+ null
+ } else {
+ notif.toNotificationChipModel()
+ }
+ }
+
+ private fun ActiveNotificationModel.toNotificationChipModel(): NotificationChipModel? {
+ val statusBarChipIconView = this.statusBarChipIconView
+ if (statusBarChipIconView == null) {
+ logger.w({ "$str1: Can't show chip because status bar chip icon view is null" }) {
+ str1 = extraLogTag
+ }
+ return null
+ }
+ return NotificationChipModel(key, statusBarChipIconView)
+ }
+
+ @AssistedFactory
+ fun interface Factory {
+ fun create(startingModel: ActiveNotificationModel): SingleNotificationChipInteractor
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractor.kt
index 9e09671bc7bf..e8cb35b06999 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractor.kt
@@ -17,16 +17,42 @@
package com.android.systemui.statusbar.chips.notification.domain.interactor
import android.annotation.SuppressLint
+import com.android.app.tracing.coroutines.launchTraced as launch
+import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.Logger
+import com.android.systemui.statusbar.chips.StatusBarChipLogTags.pad
+import com.android.systemui.statusbar.chips.StatusBarChipsLog
+import com.android.systemui.statusbar.chips.notification.domain.model.NotificationChipModel
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
+import com.android.systemui.util.kotlin.pairwise
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.asSharedFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
/** An interactor for the notification chips shown in the status bar. */
@SysUISingleton
-class StatusBarNotificationChipsInteractor @Inject constructor() {
+@OptIn(ExperimentalCoroutinesApi::class)
+class StatusBarNotificationChipsInteractor
+@Inject
+constructor(
+ @Background private val backgroundScope: CoroutineScope,
+ private val activeNotificationsInteractor: ActiveNotificationsInteractor,
+ private val singleNotificationChipInteractorFactory: SingleNotificationChipInteractor.Factory,
+ @StatusBarChipsLog private val logBuffer: LogBuffer,
+) : CoreStartable {
+ private val logger = Logger(logBuffer, "AllNotifs".pad())
// Each chip tap is an individual event, *not* a state, which is why we're using SharedFlow not
// StateFlow. There shouldn't be multiple updates per frame, which should avoid performance
@@ -45,4 +71,79 @@ class StatusBarNotificationChipsInteractor @Inject constructor() {
StatusBarNotifChips.assertInNewMode()
_promotedNotificationChipTapEvent.emit(key)
}
+
+ /**
+ * A cache of interactors. Each currently-promoted notification should have a corresponding
+ * interactor in this map.
+ */
+ private val promotedNotificationInteractorMap =
+ mutableMapOf<String, SingleNotificationChipInteractor>()
+
+ /**
+ * A list of interactors. Each currently-promoted notification should have a corresponding
+ * interactor in this list.
+ */
+ private val promotedNotificationInteractors =
+ MutableStateFlow<List<SingleNotificationChipInteractor>>(emptyList())
+
+ override fun start() {
+ if (!StatusBarNotifChips.isEnabled) {
+ return
+ }
+
+ backgroundScope.launch("StatusBarNotificationChipsInteractor") {
+ activeNotificationsInteractor.promotedOngoingNotifications
+ .pairwise(initialValue = emptyList())
+ .collect { (oldNotifs, currentNotifs) ->
+ val removedNotifs = oldNotifs.minus(currentNotifs.toSet())
+ removedNotifs.forEach { removedNotif ->
+ val wasRemoved = promotedNotificationInteractorMap.remove(removedNotif.key)
+ if (wasRemoved == null) {
+ logger.w({
+ "Attempted to remove $str1 from interactor map but it wasn't present"
+ }) {
+ str1 = removedNotif.key
+ }
+ }
+ }
+ currentNotifs.forEach { notif ->
+ val interactor =
+ promotedNotificationInteractorMap.computeIfAbsent(notif.key) {
+ singleNotificationChipInteractorFactory.create(notif)
+ }
+ interactor.setNotification(notif)
+ }
+ logger.d({ "Interactors: $str1" }) {
+ str1 =
+ promotedNotificationInteractorMap.keys.joinToString(separator = " /// ")
+ }
+ promotedNotificationInteractors.value =
+ promotedNotificationInteractorMap.values.toList()
+ }
+ }
+ }
+
+ /**
+ * A flow modeling the notifications that should be shown as chips in the status bar. Emits an
+ * empty list if there are no notifications that should show a status bar chip.
+ */
+ val notificationChips: Flow<List<NotificationChipModel>> =
+ if (StatusBarNotifChips.isEnabled) {
+ // For all our current interactors...
+ promotedNotificationInteractors.flatMapLatest { interactors ->
+ if (interactors.isNotEmpty()) {
+ // Combine each interactor's [notificationChip] flow...
+ val allNotificationChips: List<Flow<NotificationChipModel?>> =
+ interactors.map { interactor -> interactor.notificationChip }
+ combine(allNotificationChips) {
+ // ... and emit just the non-null chips
+ it.filterNotNull()
+ }
+ } else {
+ flowOf(emptyList())
+ }
+ }
+ } else {
+ flowOf(emptyList())
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt
new file mode 100644
index 000000000000..5698ee6d1917
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.chips.notification.domain.model
+
+import com.android.systemui.statusbar.StatusBarIconView
+
+/** Modeling all the data needed to render a status bar notification chip. */
+data class NotificationChipModel(val key: String, val statusBarChipIconView: StatusBarIconView)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
index 752674854e2d..9eff627c8714 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
@@ -20,11 +20,10 @@ import android.view.View
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.statusbar.chips.notification.domain.interactor.StatusBarNotificationChipsInteractor
+import com.android.systemui.statusbar.chips.notification.domain.model.NotificationChipModel
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import com.android.systemui.statusbar.chips.ui.model.ColorsModel
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
-import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
-import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
@@ -37,7 +36,6 @@ class NotifChipsViewModel
@Inject
constructor(
@Application private val applicationScope: CoroutineScope,
- activeNotificationsInteractor: ActiveNotificationsInteractor,
private val notifChipsInteractor: StatusBarNotificationChipsInteractor,
) {
/**
@@ -45,19 +43,14 @@ constructor(
* no notifications that should show a status bar chip.
*/
val chips: Flow<List<OngoingActivityChipModel.Shown>> =
- activeNotificationsInteractor.promotedOngoingNotifications.map { notifications ->
- notifications.mapNotNull { it.toChipModel() }
+ notifChipsInteractor.notificationChips.map { notifications ->
+ notifications.map { it.toActivityChipModel() }
}
- /**
- * Converts the notification to the [OngoingActivityChipModel] object. Returns null if the
- * notification has invalid data such that it can't be displayed as a chip.
- */
- private fun ActiveNotificationModel.toChipModel(): OngoingActivityChipModel.Shown? {
+ /** Converts the notification to the [OngoingActivityChipModel] object. */
+ private fun NotificationChipModel.toActivityChipModel(): OngoingActivityChipModel.Shown {
StatusBarNotifChips.assertInNewMode()
- // TODO(b/364653005): Log error if there's no icon view.
- val rawIcon = this.statusBarChipIconView ?: return null
- val icon = OngoingActivityChipModel.ChipIcon.StatusBarView(rawIcon)
+ val icon = OngoingActivityChipModel.ChipIcon.StatusBarView(this.statusBarChipIconView)
// TODO(b/364653005): Use the notification color if applicable.
val colors = ColorsModel.Themed
val onClickListener =
@@ -65,7 +58,9 @@ constructor(
// The notification pipeline needs everything to run on the main thread, so keep
// this event on the main thread.
applicationScope.launch {
- notifChipsInteractor.onPromotedNotificationChipTapped(this@toChipModel.key)
+ notifChipsInteractor.onPromotedNotificationChipTapped(
+ this@toActivityChipModel.key
+ )
}
}
return OngoingActivityChipModel.Shown.IconOnly(icon, colors, onClickListener)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
index 9b24d451f9cb..47b695e50a0e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
@@ -30,6 +30,7 @@ import com.android.systemui.statusbar.data.repository.LightBarControllerStore
import com.android.systemui.statusbar.phone.AutoHideController
import com.android.systemui.statusbar.phone.AutoHideControllerImpl
import com.android.systemui.statusbar.phone.LightBarController
+import com.android.systemui.statusbar.phone.LightBarControllerImpl
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProviderImpl
import com.android.systemui.statusbar.phone.StatusBarSignalPolicy
@@ -83,6 +84,11 @@ interface StatusBarModule {
@Binds @SysUISingleton fun autoHideController(impl: AutoHideControllerImpl): AutoHideController
+ @Binds
+ fun lightBarControllerFactory(
+ legacyFactory: LightBarControllerImpl.LegacyFactory
+ ): LightBarController.Factory
+
companion object {
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
index 9a779300de97..3825c098ca5d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
@@ -34,12 +34,12 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.inflation.BindEventManager
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
+import com.android.systemui.statusbar.notification.headsup.OnHeadsUpChangedListener
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.NotificationContentView
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinderLogger
import com.android.systemui.statusbar.notification.stack.StackStateAnimator
-import com.android.systemui.statusbar.policy.HeadsUpManager
-import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener
import com.android.systemui.util.children
import java.util.concurrent.ConcurrentHashMap
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorController.kt
index 67c53d46b4d0..383227d2b3aa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorController.kt
@@ -22,10 +22,10 @@ import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.animation.TransitionAnimator
import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
+import com.android.systemui.statusbar.notification.headsup.HeadsUpUtil
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
-import com.android.systemui.statusbar.policy.HeadsUpManager
-import com.android.systemui.statusbar.policy.HeadsUpUtil
import kotlin.math.ceil
import kotlin.math.max
@@ -36,12 +36,12 @@ class NotificationLaunchAnimatorControllerProvider(
private val notificationLaunchAnimationInteractor: NotificationLaunchAnimationInteractor,
private val notificationListContainer: NotificationListContainer,
private val headsUpManager: HeadsUpManager,
- private val jankMonitor: InteractionJankMonitor
+ private val jankMonitor: InteractionJankMonitor,
) {
@JvmOverloads
fun getAnimatorController(
notification: ExpandableNotificationRow,
- onFinishAnimationCallback: Runnable? = null
+ onFinishAnimationCallback: Runnable? = null,
): NotificationTransitionAnimatorController {
return NotificationTransitionAnimatorController(
notificationLaunchAnimationInteractor,
@@ -49,7 +49,7 @@ class NotificationLaunchAnimatorControllerProvider(
headsUpManager,
notification,
jankMonitor,
- onFinishAnimationCallback
+ onFinishAnimationCallback,
)
}
}
@@ -65,7 +65,7 @@ class NotificationTransitionAnimatorController(
private val headsUpManager: HeadsUpManager,
private val notification: ExpandableNotificationRow,
private val jankMonitor: InteractionJankMonitor,
- private val onFinishAnimationCallback: Runnable?
+ private val onFinishAnimationCallback: Runnable?,
) : ActivityTransitionAnimator.Controller {
companion object {
@@ -109,7 +109,7 @@ class NotificationTransitionAnimatorController(
left = location[0],
right = location[0] + notification.width,
topCornerRadius = topCornerRadius,
- bottomCornerRadius = notification.bottomCornerRadius
+ bottomCornerRadius = notification.bottomCornerRadius,
)
params.startTranslationZ = notification.translationZ
@@ -177,7 +177,7 @@ class NotificationTransitionAnimatorController(
row.entry.key,
true /* releaseImmediately */,
animate,
- reason
+ reason,
)
}
@@ -224,7 +224,7 @@ class NotificationTransitionAnimatorController(
override fun onTransitionAnimationProgress(
state: TransitionAnimator.State,
progress: Float,
- linearProgress: Float
+ linearProgress: Float,
) {
val params = state as LaunchAnimationParameters
params.progress = progress
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
index 08ffbf2b29d4..7a59f79c77a6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
@@ -36,14 +36,14 @@ import com.android.systemui.shade.ShadeViewController
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
+import com.android.systemui.statusbar.notification.headsup.OnHeadsUpChangedListener
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.notification.stack.StackStateAnimator
import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.phone.KeyguardBypassController.OnBypassStateChangedListener
import com.android.systemui.statusbar.phone.ScreenOffAnimationController
-import com.android.systemui.statusbar.policy.HeadsUpManager
-import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener
import com.android.systemui.util.doOnEnd
import com.android.systemui.util.doOnStart
import java.io.PrintWriter
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 c487ff5d35bd..6b84b6d07702 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
@@ -65,6 +65,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
+import com.android.systemui.statusbar.notification.headsup.PinnedStatus;
import com.android.systemui.statusbar.notification.icon.IconPack;
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -675,8 +676,8 @@ public final class NotificationEntry extends ListEntry {
return row != null && row.isPinnedAndExpanded();
}
- public void setRowPinned(boolean pinned) {
- if (row != null) row.setPinned(pinned);
+ public void setRowPinnedStatus(PinnedStatus pinnedStatus) {
+ if (row != null) row.setPinnedStatus(pinnedStatus);
}
public boolean isRowHeadsUp() {
@@ -1075,7 +1076,7 @@ public final class NotificationEntry extends ListEntry {
if (PromotedNotificationContentModel.featureFlagEnabled()) {
return mPromotedNotificationContentModel;
} else {
- Log.wtf(TAG, "getting promoted content without feature flag enabled");
+ Log.wtf(TAG, "getting promoted content without feature flag enabled", new Throwable());
return null;
}
}
@@ -1089,7 +1090,7 @@ public final class NotificationEntry extends ListEntry {
if (PromotedNotificationContentModel.featureFlagEnabled()) {
this.mPromotedNotificationContentModel = promotedNotificationContentModel;
} else {
- Log.wtf(TAG, "setting promoted content without feature flag enabled");
+ Log.wtf(TAG, "setting promoted content without feature flag enabled", new Throwable());
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
index c7b47eeec218..0269b16d4490 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
@@ -41,13 +41,13 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.No
import com.android.systemui.statusbar.notification.collection.provider.LaunchFullScreenIntentProvider
import com.android.systemui.statusbar.notification.collection.render.NodeController
import com.android.systemui.statusbar.notification.dagger.IncomingHeader
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
+import com.android.systemui.statusbar.notification.headsup.OnHeadsUpChangedListener
import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider
import com.android.systemui.statusbar.notification.logKey
import com.android.systemui.statusbar.notification.shared.GroupHunAnimationFix
import com.android.systemui.statusbar.notification.stack.BUCKET_HEADS_UP
-import com.android.systemui.statusbar.policy.HeadsUpManager
-import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.time.SystemClock
import java.util.function.Consumer
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinator.kt
index 366704e27b9b..de6f2576ff19 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinator.kt
@@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification.collection.coordinator
import android.annotation.SuppressLint
import androidx.annotation.VisibleForTesting
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.Dumpable
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dump.DumpManager
@@ -35,8 +36,8 @@ import com.android.systemui.statusbar.notification.collection.coordinator.dagger
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
-import com.android.systemui.statusbar.policy.HeadsUpManager
-import com.android.systemui.statusbar.policy.headsUpEvents
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
+import com.android.systemui.statusbar.notification.headsup.headsUpEvents
import com.android.systemui.util.asIndenting
import com.android.systemui.util.indentIfPossible
import java.io.PrintWriter
@@ -52,7 +53,6 @@ import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
-import com.android.app.tracing.coroutines.launchTraced as launch
import kotlinx.coroutines.yield
/**
@@ -123,7 +123,7 @@ constructor(
unseenNotifications.removeAll(notificationsSeenWhileLocked)
logger.logAllMarkedSeenOnUnlock(
seenCount = notificationsSeenWhileLocked.size,
- remainingUnseenCount = unseenNotifications.size
+ remainingUnseenCount = unseenNotifications.size,
)
notificationsSeenWhileLocked.clear()
}
@@ -140,7 +140,7 @@ constructor(
* been "seen" while the device is on the keyguard.
*/
private suspend fun trackSeenNotificationsWhileLocked(
- notificationsSeenWhileLocked: MutableSet<NotificationEntry>,
+ notificationsSeenWhileLocked: MutableSet<NotificationEntry>
) = coroutineScope {
// Remove removed notifications from the set
launch {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
index 8660cd117493..e75c11de57c7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
@@ -45,7 +45,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga
import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor;
import com.android.systemui.statusbar.notification.shared.NotificationMinimalism;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.kotlin.BooleanFlowOperators;
import com.android.systemui.util.kotlin.JavaAdapter;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java
index 5743ab0eae27..07fa6aeb7900 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java
@@ -34,7 +34,7 @@ import com.android.systemui.statusbar.notification.collection.coordinator.Visual
import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.row.OnUserInteractionCallback;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
import javax.inject.Inject;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index 42aadd132cd8..8a1371f1c415 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -77,6 +77,10 @@ import com.android.systemui.statusbar.notification.interruption.VisualInterrupti
import com.android.systemui.statusbar.notification.logging.NotificationPanelLogger;
import com.android.systemui.statusbar.notification.logging.NotificationPanelLoggerImpl;
import com.android.systemui.statusbar.notification.logging.dagger.NotificationsLogModule;
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractor;
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationLogger;
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationsProvider;
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel;
import com.android.systemui.statusbar.notification.row.NotificationEntryProcessorFactory;
import com.android.systemui.statusbar.notification.row.NotificationEntryProcessorFactoryLooperImpl;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -88,7 +92,7 @@ import com.android.systemui.statusbar.notification.stack.NotificationStackScroll
import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.StatusBarNotificationActivityStarter;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
import com.android.systemui.statusbar.policy.ZenModesCleanupStartable;
import dagger.Binds;
@@ -101,6 +105,8 @@ import kotlin.coroutines.CoroutineContext;
import kotlinx.coroutines.CoroutineScope;
+import java.util.Optional;
+
import javax.inject.Provider;
/**
@@ -308,4 +314,22 @@ public interface NotificationsModule {
@IntoMap
@ClassKey(ZenModesCleanupStartable.class)
CoreStartable bindsZenModesCleanup(ZenModesCleanupStartable zenModesCleanup);
+
+ /**
+ * Provides {@link
+ * com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractor} if
+ * one of the relevant feature flags is enabled.
+ */
+ @Provides
+ @SysUISingleton
+ static Optional<PromotedNotificationContentExtractor>
+ providePromotedNotificationContentExtractor(
+ PromotedNotificationsProvider provider, Context context,
+ PromotedNotificationLogger logger) {
+ if (PromotedNotificationContentModel.featureFlagEnabled()) {
+ return Optional.of(new PromotedNotificationContentExtractor(provider, context, logger));
+ } else {
+ return Optional.empty();
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt
index cf4fb25f638e..375983543c30 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationDataLayerModule.kt
@@ -16,11 +16,11 @@
package com.android.systemui.statusbar.notification.data
import com.android.systemui.statusbar.notification.data.repository.HeadsUpRepository
-import com.android.systemui.statusbar.policy.BaseHeadsUpManager
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManagerImpl
import dagger.Binds
import dagger.Module
@Module(includes = [NotificationSettingsRepositoryModule::class])
interface NotificationDataLayerModule {
- @Binds fun bindHeadsUpNotificationRepository(impl: BaseHeadsUpManager): HeadsUpRepository
+ @Binds fun bindHeadsUpNotificationRepository(impl: HeadsUpManagerImpl): HeadsUpRepository
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpRowRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpRowRepository.kt
index 7b40812d55c3..266310479a1a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpRowRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/HeadsUpRowRepository.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.data.repository
+import com.android.systemui.statusbar.notification.headsup.PinnedStatus
import com.android.systemui.statusbar.notification.shared.HeadsUpRowKey
import kotlinx.coroutines.flow.StateFlow
@@ -31,6 +32,6 @@ interface HeadsUpRowRepository : HeadsUpRowKey {
/** A key to identify this row in the view hierarchy. */
val elementKey: Any
- /** Whether this notification is "pinned", meaning that it should stay on top of the screen. */
- val isPinned: StateFlow<Boolean>
+ /** This notification's pinning status. */
+ val pinnedStatus: StateFlow<PinnedStatus>
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
index e25127e3e0d6..81335658679b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt
@@ -52,7 +52,9 @@ constructor(
val topHeadsUpRowIfPinned: Flow<HeadsUpRowKey?> =
headsUpRepository.topHeadsUpRow
.flatMapLatest { repository ->
- repository?.isPinned?.map { pinned -> repository.takeIf { pinned } } ?: flowOf(null)
+ repository?.pinnedStatus?.map { pinnedStatus ->
+ repository.takeIf { pinnedStatus.isPinned }
+ } ?: flowOf(null)
}
.distinctUntilChanged()
@@ -64,7 +66,7 @@ constructor(
if (repositories.isNotEmpty()) {
val toCombine: List<Flow<Pair<HeadsUpRowRepository, Boolean>>> =
repositories.map { repo ->
- repo.isPinned.map { isPinned -> repo to isPinned }
+ repo.pinnedStatus.map { pinnedStatus -> repo to pinnedStatus.isPinned }
}
combine(toCombine) { pairs -> pairs.toSet() }
} else {
@@ -102,7 +104,9 @@ constructor(
} else {
headsUpRepository.activeHeadsUpRows.flatMapLatest { rows ->
if (rows.isNotEmpty()) {
- combine(rows.map { it.isPinned }) { pins -> pins.any { it } }
+ combine(rows.map { it.pinnedStatus }) { pinnedStatus ->
+ pinnedStatus.any { it.isPinned }
+ }
} else {
// if the set is empty, there are no flows to combine
flowOf(false)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/AvalancheController.kt
index d406ed4e49c7..5c7c020a3bd9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/AvalancheController.kt
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.systemui.statusbar.policy
+package com.android.systemui.statusbar.notification.headsup
import android.os.Handler
import android.util.Log
@@ -24,29 +24,30 @@ import com.android.systemui.Dumpable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManagerImpl.HeadsUpEntry
import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
-import com.android.systemui.statusbar.policy.BaseHeadsUpManager.HeadsUpEntry
import com.android.systemui.util.Compile
import java.io.PrintWriter
import javax.inject.Inject
/*
* Control when heads up notifications show during an avalanche where notifications arrive in fast
- * succession, by delaying visual listener side effects and removal handling from BaseHeadsUpManager
+ * succession, by delaying visual listener side effects and removal handling from
+ * [HeadsUpManagerImpl].
*/
@SysUISingleton
class AvalancheController
@Inject
constructor(
- dumpManager: DumpManager,
- private val uiEventLogger: UiEventLogger,
- private val headsUpManagerLogger: HeadsUpManagerLogger,
- @Background private val bgHandler: Handler
+ dumpManager: DumpManager,
+ private val uiEventLogger: UiEventLogger,
+ private val headsUpManagerLogger: HeadsUpManagerLogger,
+ @Background private val bgHandler: Handler,
) : Dumpable {
private val tag = "AvalancheController"
private val debug = Compile.IS_DEBUG && Log.isLoggable(tag, Log.DEBUG)
- var baseEntryMapStr : () -> String = { "baseEntryMapStr not initialized" }
+ var baseEntryMapStr: () -> String = { "baseEntryMapStr not initialized" }
var enableAtRuntime = true
set(value) {
@@ -119,23 +120,29 @@ constructor(
if (runnable == null) {
headsUpManagerLogger.logAvalancheUpdate(
- caller, isEnabled, key,
- "Runnable NULL, stop. ${getStateStr()}"
+ caller,
+ isEnabled,
+ key,
+ "Runnable NULL, stop. ${getStateStr()}",
)
return
}
if (!isEnabled) {
headsUpManagerLogger.logAvalancheUpdate(
- caller, isEnabled, key,
- "NOT ENABLED, run runnable. ${getStateStr()}"
+ caller,
+ isEnabled,
+ key,
+ "NOT ENABLED, run runnable. ${getStateStr()}",
)
runnable.run()
return
}
if (entry == null) {
headsUpManagerLogger.logAvalancheUpdate(
- caller, isEnabled, key,
- "Entry NULL, stop. ${getStateStr()}"
+ caller,
+ isEnabled,
+ key,
+ "Entry NULL, stop. ${getStateStr()}",
)
return
}
@@ -168,7 +175,7 @@ constructor(
headsUpEntryShowing!!.updateEntry(
/* updatePostTime= */ false,
/* updateEarliestRemovalTime= */ false,
- /* reason= */ "avalanche duration update"
+ /* reason= */ "avalanche duration update",
)
}
}
@@ -192,24 +199,30 @@ constructor(
if (runnable == null) {
headsUpManagerLogger.logAvalancheDelete(
- caller, isEnabled, key,
- "Runnable NULL, stop. ${getStateStr()}"
+ caller,
+ isEnabled,
+ key,
+ "Runnable NULL, stop. ${getStateStr()}",
)
return
}
if (!isEnabled) {
runnable.run()
headsUpManagerLogger.logAvalancheDelete(
- caller, isEnabled = false, key,
- "NOT ENABLED, run runnable. ${getStateStr()}"
+ caller,
+ isEnabled = false,
+ key,
+ "NOT ENABLED, run runnable. ${getStateStr()}",
)
return
}
if (entry == null) {
runnable.run()
headsUpManagerLogger.logAvalancheDelete(
- caller, isEnabled = true, key,
- "Entry NULL, run runnable. ${getStateStr()}"
+ caller,
+ isEnabled = true,
+ key,
+ "Entry NULL, run runnable. ${getStateStr()}",
)
return
}
@@ -219,11 +232,9 @@ constructor(
if (entry in nextList) nextList.remove(entry)
uiEventLogger.log(ThrottleEvent.AVALANCHE_THROTTLING_HUN_REMOVED)
outcome = "remove from next. ${getStateStr()}"
-
} else if (entry in debugDropSet) {
debugDropSet.remove(entry)
outcome = "remove from dropset. ${getStateStr()}"
-
} else if (isShowing(entry)) {
previousHunKey = getKey(headsUpEntryShowing)
// Show the next HUN before removing this one, so that we don't tell listeners
@@ -233,7 +244,6 @@ constructor(
showNext()
runnable.run()
outcome = "remove showing. ${getStateStr()}"
-
} else {
runnable.run()
outcome = "run runnable for untracked shown HUN. ${getStateStr()}"
@@ -421,13 +431,13 @@ constructor(
private fun getStateStr(): String {
return "\navalanche state:" +
- "\n\tshowing: [${getKey(headsUpEntryShowing)}]" +
- "\n\tprevious: [$previousHunKey]" +
- "\n\tnext list: $nextListStr" +
- "\n\tnext map: $nextMapStr" +
- "\n\tdropped: $dropSetStr" +
- "\nBHUM.mHeadsUpEntryMap: " +
- baseEntryMapStr()
+ "\n\tshowing: [${getKey(headsUpEntryShowing)}]" +
+ "\n\tprevious: [$previousHunKey]" +
+ "\n\tnext list: $nextListStr" +
+ "\n\tnext map: $nextMapStr" +
+ "\n\tdropped: $dropSetStr" +
+ "\nBHUM.mHeadsUpEntryMap: " +
+ baseEntryMapStr()
}
private val dropSetStr: String
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManager.kt
index b37194b8b7a0..424a3c5e6af9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManager.kt
@@ -1,4 +1,20 @@
-package com.android.systemui.statusbar.policy
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.headsup
import android.graphics.Region
import com.android.systemui.Dumpable
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerExt.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerExt.kt
index 5e367509e774..6525b6f1186b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerExt.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.policy
+package com.android.systemui.statusbar.notification.headsup
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.statusbar.notification.collection.NotificationEntry
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java
index 298ef7ee4bfa..99df9f45840a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImpl.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.policy;
+package com.android.systemui.statusbar.notification.headsup;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -55,6 +55,8 @@ import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun;
import com.android.systemui.statusbar.phone.ExpandHeadsUpOnInlineReply;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
+import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.util.ListenerSet;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.kotlin.JavaAdapter;
@@ -84,7 +86,7 @@ import kotlinx.coroutines.flow.StateFlowKt;
* they simply peek from the top of the screen.
*/
@SysUISingleton
-public class BaseHeadsUpManager
+public class HeadsUpManagerImpl
implements HeadsUpManager, HeadsUpRepository, OnHeadsUpChangedListener {
private static final String TAG = "BaseHeadsUpManager";
private static final String SETTING_HEADS_UP_SNOOZE_LENGTH_MS = "heads_up_snooze_length_ms";
@@ -180,7 +182,7 @@ public class BaseHeadsUpManager
}
@Inject
- public BaseHeadsUpManager(
+ public HeadsUpManagerImpl(
@NonNull final Context context,
HeadsUpManagerLogger logger,
StatusBarStateController statusBarStateController,
@@ -403,9 +405,11 @@ public class BaseHeadsUpManager
}
if (shouldHeadsUpAgain) {
headsUpEntry.updateEntry(true /* updatePostTime */, "updateNotification");
+ PinnedStatus pinnedStatus = shouldHeadsUpBecomePinned(headsUpEntry.mEntry)
+ ? PinnedStatus.PinnedBySystem
+ : PinnedStatus.NotPinned;
if (headsUpEntry != null) {
- setEntryPinned(headsUpEntry, shouldHeadsUpBecomePinned(headsUpEntry.mEntry),
- "updateNotificationInternal");
+ setEntryPinned(headsUpEntry, pinnedStatus, "updateNotificationInternal");
}
}
}
@@ -417,7 +421,7 @@ public class BaseHeadsUpManager
@Override
public boolean shouldSwallowClick(@NonNull String key) {
- BaseHeadsUpManager.HeadsUpEntry entry = getHeadsUpEntry(key);
+ HeadsUpManagerImpl.HeadsUpEntry entry = getHeadsUpEntry(key);
return entry != null && mSystemClock.elapsedRealtime() < entry.mPostTime;
}
@@ -560,15 +564,16 @@ public class BaseHeadsUpManager
}
protected void setEntryPinned(
- @NonNull BaseHeadsUpManager.HeadsUpEntry headsUpEntry, boolean isPinned,
+ @NonNull HeadsUpManagerImpl.HeadsUpEntry headsUpEntry, PinnedStatus pinnedStatus,
String reason) {
- mLogger.logSetEntryPinned(headsUpEntry.mEntry, isPinned, reason);
+ mLogger.logSetEntryPinned(headsUpEntry.mEntry, pinnedStatus, reason);
NotificationEntry entry = headsUpEntry.mEntry;
+ boolean isPinned = pinnedStatus.isPinned();
if (!isPinned) {
headsUpEntry.mWasUnpinned = true;
}
- if (headsUpEntry.isRowPinned() != isPinned) {
- headsUpEntry.setRowPinned(isPinned);
+ if (headsUpEntry.getPinnedStatus().getValue() != pinnedStatus) {
+ headsUpEntry.setRowPinnedStatus(pinnedStatus);
updatePinnedMode();
if (isPinned && entry.getSbn() != null) {
mUiEventLogger.logWithInstanceId(
@@ -594,8 +599,10 @@ public class BaseHeadsUpManager
NotificationEntry entry = headsUpEntry.mEntry;
entry.setHeadsUp(true);
- final boolean shouldPin = shouldHeadsUpBecomePinned(entry);
- setEntryPinned(headsUpEntry, shouldPin, "onEntryAdded");
+ final PinnedStatus pinnedStatus = shouldHeadsUpBecomePinned(entry)
+ ? PinnedStatus.PinnedBySystem
+ : PinnedStatus.NotPinned;
+ setEntryPinned(headsUpEntry, pinnedStatus, "onEntryAdded");
EventLogTags.writeSysuiHeadsUpStatus(entry.getKey(), 1 /* visible */);
for (OnHeadsUpChangedListener listener : mListeners) {
listener.onHeadsUpStateChanged(entry, true);
@@ -653,7 +660,7 @@ public class BaseHeadsUpManager
protected void onEntryRemoved(HeadsUpEntry headsUpEntry, String reason) {
NotificationEntry entry = headsUpEntry.mEntry;
entry.setHeadsUp(false);
- setEntryPinned(headsUpEntry, false /* isPinned */, "onEntryRemoved");
+ setEntryPinned(headsUpEntry, PinnedStatus.NotPinned, "onEntryRemoved");
EventLogTags.writeSysuiHeadsUpStatus(entry.getKey(), 0 /* visible */);
mLogger.logNotificationActuallyRemoved(entry);
for (OnHeadsUpChangedListener listener : mListeners) {
@@ -962,7 +969,7 @@ public class BaseHeadsUpManager
Runnable runnable = () -> {
mLogger.logUnpinEntry(key);
- setEntryPinned(headsUpEntry, false /* isPinned */, "unpinAll");
+ setEntryPinned(headsUpEntry, PinnedStatus.NotPinned, "unpinAll");
// maybe it got un sticky
headsUpEntry.updateEntry(false /* updatePostTime */, "unpinAll");
@@ -1233,7 +1240,8 @@ public class BaseHeadsUpManager
@Nullable private Runnable mCancelRemoveRunnable;
private boolean mGutsShownPinned;
- private final MutableStateFlow<Boolean> mIsPinned = StateFlowKt.MutableStateFlow(false);
+ private final MutableStateFlow<PinnedStatus> mPinnedStatus =
+ StateFlowKt.MutableStateFlow(PinnedStatus.NotPinned);
/**
* If the time this entry has been on was extended
@@ -1269,8 +1277,8 @@ public class BaseHeadsUpManager
@Override
@NonNull
- public StateFlow<Boolean> isPinned() {
- return mIsPinned;
+ public StateFlow<PinnedStatus> getPinnedStatus() {
+ return mPinnedStatus;
}
/** Attach a NotificationEntry. */
@@ -1300,9 +1308,9 @@ public class BaseHeadsUpManager
return mEntry != null && mEntry.isRowPinned();
}
- protected void setRowPinned(boolean pinned) {
- if (mEntry != null) mEntry.setRowPinned(pinned);
- mIsPinned.setValue(pinned);
+ protected void setRowPinnedStatus(PinnedStatus pinnedStatus) {
+ if (mEntry != null) mEntry.setRowPinnedStatus(pinnedStatus);
+ mPinnedStatus.setValue(pinnedStatus);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerLogger.kt
index 600270c7189a..80225c47e9ea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerLogger.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.policy
+package com.android.systemui.statusbar.notification.headsup
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.LogLevel.INFO
@@ -52,7 +52,7 @@ constructor(@NotificationHeadsUpLog private val buffer: LogBuffer) {
caller: String,
isEnabled: Boolean,
notifEntryKey: String,
- outcome: String
+ outcome: String,
) {
buffer.log(
TAG,
@@ -63,7 +63,7 @@ constructor(@NotificationHeadsUpLog private val buffer: LogBuffer) {
str3 = outcome
bool1 = isEnabled
},
- { "$str1\n\t=> AC[isEnabled:$bool1] update: $str2\n\t=> $str3" }
+ { "$str1\n\t=> AC[isEnabled:$bool1] update: $str2\n\t=> $str3" },
)
}
@@ -71,7 +71,7 @@ constructor(@NotificationHeadsUpLog private val buffer: LogBuffer) {
caller: String,
isEnabled: Boolean,
notifEntryKey: String,
- outcome: String
+ outcome: String,
) {
buffer.log(
TAG,
@@ -82,7 +82,7 @@ constructor(@NotificationHeadsUpLog private val buffer: LogBuffer) {
str3 = outcome
bool1 = isEnabled
},
- { "$str1\n\t=> AC[isEnabled:$bool1] delete: $str2\n\t=> $str3" }
+ { "$str1\n\t=> AC[isEnabled:$bool1] delete: $str2\n\t=> $str3" },
)
}
@@ -99,7 +99,7 @@ constructor(@NotificationHeadsUpLog private val buffer: LogBuffer) {
long1 = delayMillis
str2 = reason
},
- { "schedule auto remove of $str1 in $long1 ms reason: $str2" }
+ { "schedule auto remove of $str1 in $long1 ms reason: $str2" },
)
}
@@ -111,7 +111,7 @@ constructor(@NotificationHeadsUpLog private val buffer: LogBuffer) {
str1 = entry.logKey
str2 = reason
},
- { "request: reschedule auto remove of $str1 reason: $str2" }
+ { "request: reschedule auto remove of $str1 reason: $str2" },
)
}
@@ -124,7 +124,7 @@ constructor(@NotificationHeadsUpLog private val buffer: LogBuffer) {
long1 = delayMillis
str2 = reason
},
- { "reschedule auto remove of $str1 in $long1 ms reason: $str2" }
+ { "reschedule auto remove of $str1 in $long1 ms reason: $str2" },
)
}
@@ -136,7 +136,7 @@ constructor(@NotificationHeadsUpLog private val buffer: LogBuffer) {
str1 = entry.logKey
str2 = reason ?: "unknown"
},
- { "request: cancel auto remove of $str1 reason: $str2" }
+ { "request: cancel auto remove of $str1 reason: $str2" },
)
}
@@ -148,7 +148,7 @@ constructor(@NotificationHeadsUpLog private val buffer: LogBuffer) {
str1 = entry.logKey
str2 = reason ?: "unknown"
},
- { "cancel auto remove of $str1 reason: $str2" }
+ { "cancel auto remove of $str1 reason: $str2" },
)
}
@@ -161,7 +161,7 @@ constructor(@NotificationHeadsUpLog private val buffer: LogBuffer) {
str2 = reason
bool1 = isWaiting
},
- { "request: $str2 => remove entry $str1 isWaiting: $isWaiting" }
+ { "request: $str2 => remove entry $str1 isWaiting: $isWaiting" },
)
}
@@ -174,7 +174,7 @@ constructor(@NotificationHeadsUpLog private val buffer: LogBuffer) {
str2 = reason
bool1 = isWaiting
},
- { "$str2 => remove entry $str1 isWaiting: $isWaiting" }
+ { "$str2 => remove entry $str1 isWaiting: $isWaiting" },
)
}
@@ -190,7 +190,7 @@ constructor(@NotificationHeadsUpLog private val buffer: LogBuffer) {
key: String,
releaseImmediately: Boolean,
isWaiting: Boolean,
- reason: String
+ reason: String,
) {
buffer.log(
TAG,
@@ -204,7 +204,7 @@ constructor(@NotificationHeadsUpLog private val buffer: LogBuffer) {
{
"remove notification $str1 releaseImmediately: $bool1 isWaiting: $bool2 " +
"reason: $str2"
- }
+ },
)
}
@@ -216,7 +216,7 @@ constructor(@NotificationHeadsUpLog private val buffer: LogBuffer) {
str1 = logKey(key)
str2 = reason
},
- { "remove notification $str1 when headsUpEntry is null, reason: $str2" }
+ { "remove notification $str1 when headsUpEntry is null, reason: $str2" },
)
}
@@ -233,7 +233,7 @@ constructor(@NotificationHeadsUpLog private val buffer: LogBuffer) {
bool1 = alert
bool2 = hasEntry
},
- { "request: update notification $str1 alert: $bool1 hasEntry: $bool2" }
+ { "request: update notification $str1 alert: $bool1 hasEntry: $bool2" },
)
}
@@ -246,7 +246,7 @@ constructor(@NotificationHeadsUpLog private val buffer: LogBuffer) {
bool1 = alert
bool2 = hasEntry
},
- { "update notification $str1 alert: $bool1 hasEntry: $bool2" }
+ { "update notification $str1 alert: $bool1 hasEntry: $bool2" },
)
}
@@ -259,7 +259,7 @@ constructor(@NotificationHeadsUpLog private val buffer: LogBuffer) {
bool1 = updatePostTime
str2 = reason ?: "unknown"
},
- { "update entry $str1 updatePostTime: $bool1 reason: $str2" }
+ { "update entry $str1 updatePostTime: $bool1 reason: $str2" },
)
}
@@ -268,20 +268,20 @@ constructor(@NotificationHeadsUpLog private val buffer: LogBuffer) {
TAG,
INFO,
{ int1 = packageSnoozeLengthMs },
- { "snooze length changed: ${int1}ms" }
+ { "snooze length changed: ${int1}ms" },
)
}
- fun logSetEntryPinned(entry: NotificationEntry, isPinned: Boolean, reason: String) {
+ fun logSetEntryPinned(entry: NotificationEntry, pinnedStatus: PinnedStatus, reason: String) {
buffer.log(
TAG,
VERBOSE,
{
str1 = entry.logKey
- bool1 = isPinned
str2 = reason
+ str3 = pinnedStatus.name
},
- { "$str2 => set entry pinned $str1 pinned: $bool1" }
+ { "$str2 => set entry pinned $str1 pinned: $str3" },
)
}
@@ -290,16 +290,12 @@ constructor(@NotificationHeadsUpLog private val buffer: LogBuffer) {
TAG,
INFO,
{ bool1 = hasPinnedNotification },
- { "has pinned notification changed to $bool1" }
+ { "has pinned notification changed to $bool1" },
)
}
fun logRemoveEntryAfterExpand(entry: NotificationEntry) {
- buffer.log(TAG, VERBOSE, {
- str1 = entry.logKey
- }, {
- "remove entry after expand: $str1"
- })
+ buffer.log(TAG, VERBOSE, { str1 = entry.logKey }, { "remove entry after expand: $str1" })
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpModule.kt
index 83551e9b8294..f9502eeff270 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpModule.kt
@@ -14,15 +14,13 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.phone
+package com.android.systemui.statusbar.notification.headsup
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.statusbar.policy.BaseHeadsUpManager
-import com.android.systemui.statusbar.policy.HeadsUpManager
import dagger.Binds
import dagger.Module
@Module
interface HeadsUpModule {
- @Binds @SysUISingleton fun bindsHeadsUpManager(hum: BaseHeadsUpManager): HeadsUpManager
+ @Binds @SysUISingleton fun bindsHeadsUpManager(hum: HeadsUpManagerImpl): HeadsUpManager
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeadsUpNotificationViewControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpNotificationViewControllerEmptyImpl.kt
index 021d30144b32..84754e530055 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeadsUpNotificationViewControllerEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpNotificationViewControllerEmptyImpl.kt
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification
+package com.android.systemui.statusbar.notification.headsup
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
-import com.android.systemui.statusbar.notification.HeadsUpTouchHelper.HeadsUpNotificationViewController
+import com.android.systemui.statusbar.notification.headsup.HeadsUpTouchHelper.HeadsUpNotificationViewController
/** Empty impl of [HeadsUpNotificationViewController] for use with Scene Container */
class HeadsUpNotificationViewControllerEmptyImpl : HeadsUpNotificationViewController {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeadsUpTouchHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpTouchHelper.java
index a61463823613..3b6b9ed636fe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeadsUpTouchHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpTouchHelper.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification;
+package com.android.systemui.statusbar.notification.headsup;
import android.content.Context;
import android.os.RemoteException;
@@ -27,7 +27,6 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
/**
* A helper class to handle touches on the heads-up views.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpUtil.java
index f4a1975fab52..40da232ae598 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpUtil.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpUtil.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.policy;
+package com.android.systemui.statusbar.notification.headsup;
import android.annotation.NonNull;
import android.annotation.Nullable;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/OnHeadsUpChangedListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/OnHeadsUpChangedListener.java
index de3bf0462d5b..b1fd78402570 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/OnHeadsUpChangedListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/OnHeadsUpChangedListener.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.policy;
+package com.android.systemui.statusbar.notification.headsup;
import android.annotation.NonNull;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/PinnedStatus.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/PinnedStatus.kt
new file mode 100644
index 000000000000..af1805476f05
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/PinnedStatus.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.headsup
+
+/**
+ * A status representing whether and how a notification is pinned.
+ *
+ * @property isPinned true if a notification should be "pinned", meaning that a notification should
+ * stay on top of the screen.
+ */
+enum class PinnedStatus(val isPinned: Boolean) {
+ /** This notification is not pinned. */
+ NotPinned(isPinned = false),
+ /**
+ * This notification is pinned by the system - likely because when the notification was added or
+ * updated, it required pinning.
+ */
+ PinnedBySystem(isPinned = true),
+ /**
+ * This notification is pinned because the user did an explicit action to pin it (like tapping
+ * the notification chip in the status bar).
+ */
+ // TODO(b/364653005): Use this status when a user taps the notification chip.
+ PinnedByUser(isPinned = true),
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/StatusBarHeadsUpChangeListener.java
index 7145ffe1f515..23eb50700060 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/StatusBarHeadsUpChangeListener.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.statusbar.notification.headsup;
import com.android.systemui.CoreStartable;
import com.android.systemui.dagger.SysUISingleton;
@@ -26,14 +26,13 @@ import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
+import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.window.StatusBarWindowControllerStore;
import javax.inject.Inject;
/**
- * Ties the status bar to {@link com.android.systemui.statusbar.policy.HeadsUpManager}.
+ * Ties the status bar to {@link HeadsUpManager}.
*/
@SysUISingleton
public class StatusBarHeadsUpChangeListener implements OnHeadsUpChangedListener, CoreStartable {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
index ec0827b4c478..caa6ccf9a5fa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
@@ -52,13 +52,13 @@ import com.android.systemui.settings.UserTracker
import com.android.systemui.shared.notifications.domain.interactor.NotificationSettingsInteractor
import com.android.systemui.statusbar.StatusBarState.SHADE
import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.MAX_HUN_WHEN_AGE_MS
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.HUN_SUPPRESSED_OLD_WHEN
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.BUBBLE
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PEEK
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PULSE
import com.android.systemui.statusbar.policy.BatteryController
-import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.util.NotificationChannels
import com.android.systemui.util.settings.GlobalSettings
import com.android.systemui.util.settings.SystemSettings
@@ -102,7 +102,7 @@ class PeekDisabledSuppressor(
globalSettings.registerContentObserverSync(
globalSettings.getUriFor(HEADS_UP_NOTIFICATIONS_ENABLED),
/* notifyForDescendants = */ true,
- observer
+ observer,
)
// QQQ: Do we need to register for SETTING_HEADS_UP_TICKER? It seems unused.
@@ -139,7 +139,7 @@ class PeekPackageSnoozedSuppressor(private val headsUpManager: HeadsUpManager) :
class PeekAlreadyBubbledSuppressor(
private val statusBarStateController: StatusBarStateController,
- private val bubbles: Optional<Bubbles>
+ private val bubbles: Optional<Bubbles>,
) : VisualInterruptionFilter(types = setOf(PEEK), reason = "already bubbled") {
override fun shouldSuppress(entry: NotificationEntry) =
when {
@@ -164,7 +164,7 @@ class PeekNotImportantSuppressor :
class PeekDeviceNotInUseSuppressor(
private val powerManager: PowerManager,
- private val statusBarStateController: StatusBarStateController
+ private val statusBarStateController: StatusBarStateController,
) : VisualInterruptionCondition(types = setOf(PEEK), reason = "device not in use") {
override fun shouldSuppress() =
when {
@@ -177,7 +177,7 @@ class PeekOldWhenSuppressor(private val systemClock: SystemClock) :
VisualInterruptionFilter(
types = setOf(PEEK),
reason = "has old `when`",
- uiEventId = HUN_SUPPRESSED_OLD_WHEN
+ uiEventId = HUN_SUPPRESSED_OLD_WHEN,
) {
private fun whenAge(entry: NotificationEntry) =
systemClock.currentTimeMillis() - entry.sbn.notification.getWhen()
@@ -206,7 +206,7 @@ class PulseEffectSuppressor :
class PulseLockscreenVisibilityPrivateSuppressor :
VisualInterruptionFilter(
types = setOf(PULSE),
- reason = "hidden by lockscreen visibility override"
+ reason = "hidden by lockscreen visibility override",
) {
override fun shouldSuppress(entry: NotificationEntry) =
entry.ranking.lockscreenVisibilityOverride == VISIBILITY_PRIVATE
@@ -220,7 +220,7 @@ class PulseLowImportanceSuppressor :
class HunGroupAlertBehaviorSuppressor :
VisualInterruptionFilter(
types = setOf(PEEK, PULSE),
- reason = "suppressive group alert behavior"
+ reason = "suppressive group alert behavior",
) {
override fun shouldSuppress(entry: NotificationEntry) =
entry.sbn.let { it.isGroup && it.notification.suppressAlertingDueToGrouping() }
@@ -282,11 +282,7 @@ class AvalancheSuppressor(
private val notificationManager: NotificationManager,
private val logger: VisualInterruptionDecisionLogger,
private val systemSettings: SystemSettings,
-) :
- VisualInterruptionFilter(
- types = setOf(PEEK, PULSE),
- reason = "avalanche",
- ) {
+) : VisualInterruptionFilter(types = setOf(PEEK, PULSE), reason = "avalanche") {
val TAG = "AvalancheSuppressor"
private val prefs = context.getSharedPreferences(context.packageName, Context.MODE_PRIVATE)
@@ -324,7 +320,7 @@ class AvalancheSuppressor(
ALLOW_FSI_WITH_PERMISSION_ON,
ALLOW_COLORIZED,
ALLOW_EMERGENCY,
- SUPPRESS
+ SUPPRESS,
}
enum class AvalancheEvent(private val id: Int) : UiEventLogger.UiEventEnum {
@@ -354,6 +350,7 @@ class AvalancheSuppressor(
AVALANCHE_SUPPRESSOR_HUN_ALLOWED_CATEGORY_CAR_EMERGENCY(1868),
@UiEvent(doc = "HUN allowed during avalanche because it is a car warning")
AVALANCHE_SUPPRESSOR_HUN_ALLOWED_CATEGORY_CAR_WARNING(1869);
+
override fun getId(): Int {
return id
}
@@ -399,7 +396,7 @@ class AvalancheSuppressor(
val bundle = Bundle()
bundle.putString(
Notification.EXTRA_SUBSTITUTE_APP_NAME,
- context.getString(com.android.internal.R.string.android_system_label)
+ context.getString(com.android.internal.R.string.android_system_label),
)
val builder =
@@ -452,7 +449,8 @@ class AvalancheSuppressor(
if (entry.sbn.notification.category == CATEGORY_CAR_EMERGENCY) {
uiEventLogger.log(
- AvalancheEvent.AVALANCHE_SUPPRESSOR_HUN_ALLOWED_CATEGORY_CAR_EMERGENCY)
+ AvalancheEvent.AVALANCHE_SUPPRESSOR_HUN_ALLOWED_CATEGORY_CAR_EMERGENCY
+ )
return State.ALLOW_CATEGORY_CAR_EMERGENCY
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index 450067a969e2..f586051feb56 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
@@ -47,7 +47,7 @@ import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.EventLog;
import com.android.systemui.util.settings.GlobalSettings;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
index 52336be742cd..b831b9457acd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
@@ -32,6 +32,7 @@ import com.android.systemui.settings.UserTracker
import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.shared.notifications.domain.interactor.NotificationSettingsInteractor
import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider.Decision
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider.FullScreenIntentDecision
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionSuppressor.EventLogData
@@ -41,7 +42,6 @@ import com.android.systemui.statusbar.notification.interruption.VisualInterrupti
import com.android.systemui.statusbar.notification.shared.NotificationAvalancheSuppression
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.DeviceProvisionedController
-import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.EventLog
import com.android.systemui.util.settings.GlobalSettings
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt
new file mode 100644
index 000000000000..f400d605cb43
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.promoted
+
+import android.app.Notification
+import android.app.Notification.BigPictureStyle
+import android.app.Notification.BigTextStyle
+import android.app.Notification.CallStyle
+import android.app.Notification.EXTRA_CHRONOMETER_COUNT_DOWN
+import android.app.Notification.EXTRA_SUB_TEXT
+import android.app.Notification.EXTRA_TEXT
+import android.app.Notification.EXTRA_TITLE
+import android.app.Notification.ProgressStyle
+import android.content.Context
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel.Style
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel.When
+import javax.inject.Inject
+
+@SysUISingleton
+class PromotedNotificationContentExtractor
+@Inject
+constructor(
+ private val promotedNotificationsProvider: PromotedNotificationsProvider,
+ private val context: Context,
+ private val logger: PromotedNotificationLogger,
+) {
+ fun extractContent(
+ entry: NotificationEntry,
+ recoveredBuilder: Notification.Builder,
+ ): PromotedNotificationContentModel? {
+ if (!PromotedNotificationContentModel.featureFlagEnabled()) {
+ logger.logExtractionSkipped(entry, "feature flags disabled")
+ return null
+ }
+
+ if (!promotedNotificationsProvider.shouldPromote(entry)) {
+ logger.logExtractionSkipped(entry, "shouldPromote returned false")
+ return null
+ }
+
+ val notification = entry.sbn.notification
+ if (notification == null) {
+ logger.logExtractionFailed(entry, "entry.sbn.notification is null")
+ return null
+ }
+
+ val contentBuilder = PromotedNotificationContentModel.Builder(entry.key)
+
+ // TODO: Pitch a fit if style is unsupported or mandatory fields are missing once
+ // FLAG_PROMOTED_ONGOING is set reliably and we're not testing status bar chips.
+
+ contentBuilder.skeletonSmallIcon = entry.icons.aodIcon?.sourceIcon
+ contentBuilder.appName = notification.loadHeaderAppName(context)
+ contentBuilder.subText = notification.subText()
+ contentBuilder.time = notification.extractWhen()
+ contentBuilder.lastAudiblyAlertedMs = entry.lastAudiblyAlertedMs
+ contentBuilder.profileBadgeResId = null // TODO
+ contentBuilder.title = notification.title()
+ contentBuilder.text = notification.text()
+ contentBuilder.skeletonLargeIcon = null // TODO
+
+ recoveredBuilder.style?.extractContent(contentBuilder)
+ ?: run { contentBuilder.style = Style.Ineligible }
+
+ return contentBuilder.build().also { logger.logExtractionSucceeded(entry, it) }
+ }
+}
+
+private fun Notification.title(): CharSequence? = extras?.getCharSequence(EXTRA_TITLE)
+
+private fun Notification.text(): CharSequence? = extras?.getCharSequence(EXTRA_TEXT)
+
+private fun Notification.subText(): String? = extras?.getString(EXTRA_SUB_TEXT)
+
+private fun Notification.chronometerCountDown(): Boolean =
+ extras?.getBoolean(EXTRA_CHRONOMETER_COUNT_DOWN, /* defaultValue= */ false) ?: false
+
+private fun Notification.extractWhen(): When? {
+ val time = `when`
+ val showsTime = showsTime()
+ val showsChronometer = showsChronometer()
+ val countDown = chronometerCountDown()
+
+ return when {
+ showsTime -> When(time, When.Mode.Absolute)
+ showsChronometer -> When(time, if (countDown) When.Mode.CountDown else When.Mode.CountUp)
+ else -> null
+ }
+}
+
+private fun Notification.Style.extractContent(
+ contentBuilder: PromotedNotificationContentModel.Builder
+) {
+ contentBuilder.style =
+ when (this) {
+ is BigPictureStyle -> {
+ extractContent(contentBuilder)
+ Style.BigPicture
+ }
+
+ is BigTextStyle -> {
+ extractContent(contentBuilder)
+ Style.BigText
+ }
+
+ is CallStyle -> {
+ extractContent(contentBuilder)
+ Style.Call
+ }
+
+ is ProgressStyle -> {
+ extractContent(contentBuilder)
+ Style.Progress
+ }
+
+ else -> Style.Ineligible
+ }
+}
+
+private fun BigPictureStyle.extractContent(
+ contentBuilder: PromotedNotificationContentModel.Builder
+) {
+ // TODO?
+}
+
+private fun BigTextStyle.extractContent(contentBuilder: PromotedNotificationContentModel.Builder) {
+ // TODO?
+}
+
+private fun CallStyle.extractContent(contentBuilder: PromotedNotificationContentModel.Builder) {
+ contentBuilder.personIcon = null // TODO
+ contentBuilder.personName = null // TODO
+ contentBuilder.verificationIcon = null // TODO
+ contentBuilder.verificationText = null // TODO
+}
+
+private fun ProgressStyle.extractContent(contentBuilder: PromotedNotificationContentModel.Builder) {
+ // TODO: Create NotificationProgressModel.toSkeleton, or something similar.
+ contentBuilder.progress = createProgressModel(0xffffffff.toInt(), 0x00000000)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLogger.kt
new file mode 100644
index 000000000000..13ad1413e89d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLogger.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.promoted
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel.ERROR
+import com.android.systemui.log.core.LogLevel.INFO
+import com.android.systemui.log.dagger.NotificationLog
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.logKey
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
+import javax.inject.Inject
+
+class PromotedNotificationLogger
+@Inject
+constructor(@NotificationLog private val buffer: LogBuffer) {
+ fun logExtractionSkipped(entry: NotificationEntry, reason: String) {
+ buffer.log(
+ EXTRACTION_TAG,
+ INFO,
+ {
+ str1 = entry.logKey
+ str2 = reason
+ },
+ { "extraction skipped: $str2 for $str1" },
+ )
+ }
+
+ fun logExtractionFailed(entry: NotificationEntry, reason: String) {
+ buffer.log(
+ EXTRACTION_TAG,
+ ERROR,
+ {
+ str1 = entry.logKey
+ str2 = reason
+ },
+ { "extraction failed: $str2 for $str1" },
+ )
+ }
+
+ fun logExtractionSucceeded(
+ entry: NotificationEntry,
+ content: PromotedNotificationContentModel,
+ ) {
+ buffer.log(
+ EXTRACTION_TAG,
+ INFO,
+ {
+ str1 = entry.logKey
+ str2 = content.toString()
+ },
+ { "extraction succeeded: $str2 for $str1" },
+ )
+ }
+}
+
+private const val EXTRACTION_TAG = "PromotedNotificationContentExtractor"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsProvider.kt
index 691dc6f5ccac..947d9e3e9547 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsProvider.kt
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.promoted
import android.app.Notification.FLAG_PROMOTED_ONGOING
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
import javax.inject.Inject
/** A provider for making decisions on which notifications should be promoted. */
@@ -30,7 +31,7 @@ interface PromotedNotificationsProvider {
@SysUISingleton
open class PromotedNotificationsProviderImpl @Inject constructor() : PromotedNotificationsProvider {
override fun shouldPromote(entry: NotificationEntry): Boolean {
- if (!PromotedNotificationUi.isEnabled) {
+ if (!PromotedNotificationContentModel.featureFlagEnabled()) {
return false
}
return (entry.sbn.notification.flags and FLAG_PROMOTED_ONGOING) != 0
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 fb62f8000c7c..b7ab996a608c 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
@@ -36,6 +36,7 @@ import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Path;
import android.graphics.Point;
+import android.graphics.Rect;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.AnimationDrawable;
import android.graphics.drawable.Drawable;
@@ -101,6 +102,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.provider.NotificationDismissibilityProvider;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
+import com.android.systemui.statusbar.notification.headsup.PinnedStatus;
import com.android.systemui.statusbar.notification.logging.NotificationCounters;
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
@@ -108,6 +110,7 @@ import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderVi
import com.android.systemui.statusbar.notification.row.shared.LockscreenOtpRedaction;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationCompactMessagingTemplateViewWrapper;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
+import com.android.systemui.statusbar.notification.shared.NotificationAddXOnHoverToDismiss;
import com.android.systemui.statusbar.notification.shared.NotificationContentAlphaOptimization;
import com.android.systemui.statusbar.notification.shared.TransparentHeaderFix;
import com.android.systemui.statusbar.notification.stack.AmbientState;
@@ -118,13 +121,14 @@ import com.android.systemui.statusbar.notification.stack.NotificationChildrenCon
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.notification.stack.SwipeableView;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
import com.android.systemui.statusbar.policy.InflatedSmartReplyState;
import com.android.systemui.statusbar.policy.RemoteInputView;
import com.android.systemui.statusbar.policy.SmartReplyConstants;
import com.android.systemui.statusbar.policy.dagger.RemoteInputViewSubcomponent;
import com.android.systemui.util.Compile;
import com.android.systemui.util.DumpUtilsKt;
+import com.android.systemui.util.ListenerSet;
import com.android.systemui.wmshell.BubblesManager;
import java.io.PrintWriter;
@@ -277,7 +281,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
private NotificationMenuRowPlugin mMenuRow;
private ViewStub mGutsStub;
private boolean mIsSystemChildExpanded;
- private boolean mIsPinned;
+ private PinnedStatus mPinnedStatus = PinnedStatus.NotPinned;
private boolean mExpandAnimationRunning;
private AboveShelfChangedListener mAboveShelfChangedListener;
private HeadsUpManager mHeadsUpManager;
@@ -430,6 +434,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
private float mBottomRoundnessDuringLaunchAnimation;
private float mSmallRoundness;
+ private ListenerSet<DismissButtonTargetVisibilityListener>
+ mDismissButtonTargetVisibilityListeners
+ = new ListenerSet();
+
public NotificationContentView[] getLayouts() {
return Arrays.copyOf(mLayouts, mLayouts.length);
}
@@ -739,6 +747,73 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
}
+ public interface DismissButtonTargetVisibilityListener {
+ // Called when the notification dismiss button's target visibility changes.
+ // NOTE: This can be called when the dismiss button already has the target visibility.
+ void onTargetVisibilityChanged(boolean targetVisible);
+ }
+
+ public void addDismissButtonTargetStateListener(
+ DismissButtonTargetVisibilityListener listener) {
+ if (NotificationAddXOnHoverToDismiss.isUnexpectedlyInLegacyMode()) {
+ return;
+ }
+
+ mDismissButtonTargetVisibilityListeners.addIfAbsent(listener);
+ }
+
+ public void removeDismissButtonTargetStateListener(
+ DismissButtonTargetVisibilityListener listener) {
+ if (NotificationAddXOnHoverToDismiss.isUnexpectedlyInLegacyMode()) {
+ return;
+ }
+
+ mDismissButtonTargetVisibilityListeners.remove(listener);
+ }
+
+ @Override
+ public boolean onInterceptHoverEvent(MotionEvent event) {
+ if (!NotificationAddXOnHoverToDismiss.isEnabled()) {
+ return super.onInterceptHoverEvent(event);
+ }
+
+ // Do not bother checking the dismiss button's target visibility if the notification cannot
+ // be dismissed.
+ if (!canEntryBeDismissed()) {
+ return false;
+ }
+
+ final Boolean targetVisible = getDismissButtonTargetVisibilityIfAny(event);
+ if (targetVisible != null) {
+ for (DismissButtonTargetVisibilityListener listener :
+ mDismissButtonTargetVisibilityListeners) {
+ listener.onTargetVisibilityChanged(targetVisible.booleanValue());
+ }
+ }
+
+ // Do not consume the hover event so that children still have a chance to process it.
+ return false;
+ }
+
+ private @Nullable Boolean getDismissButtonTargetVisibilityIfAny(MotionEvent event) {
+ // Returns the dismiss button's target visibility resulted by `event`. Returns null if the
+ // target visibility should not change.
+
+ if (event.getAction() == MotionEvent.ACTION_HOVER_EXIT) {
+ // The notification dismiss button should be hidden when the hover exit event is located
+ // outside of the notification. NOTE: The hover exit event can be inside the
+ // notification if hover moves from one hoverable child to another.
+ final Rect localBounds = new Rect(0, 0, this.getWidth(), this.getActualHeight());
+ if (!localBounds.contains((int) event.getX(), (int) event.getY())) {
+ return Boolean.FALSE;
+ }
+ } else if (event.getAction() == MotionEvent.ACTION_HOVER_ENTER) {
+ return Boolean.TRUE;
+ }
+
+ return null;
+ }
+
private void updateLimitsForView(NotificationContentView layout) {
View contractedView = layout.getContractedChild();
boolean customView = contractedView != null
@@ -1154,17 +1229,15 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
/**
* Set this notification to be pinned to the top if {@link #isHeadsUp()} is true. By doing this
* the notification will be rendered on top of the screen.
- *
- * @param pinned whether it is pinned
*/
- public void setPinned(boolean pinned) {
+ public void setPinnedStatus(PinnedStatus pinnedStatus) {
int intrinsicHeight = getIntrinsicHeight();
boolean wasAboveShelf = isAboveShelf();
- mIsPinned = pinned;
+ mPinnedStatus = pinnedStatus;
if (intrinsicHeight != getIntrinsicHeight()) {
notifyHeightChanged(/* needsAnimation= */ false);
}
- if (pinned) {
+ if (pinnedStatus.isPinned()) {
setAnimationRunning(true);
mExpandedWhenPinned = false;
} else if (mExpandedWhenPinned) {
@@ -1178,7 +1251,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
@Override
public boolean isPinned() {
- return mIsPinned;
+ return mPinnedStatus.isPinned();
}
@Override
@@ -2218,6 +2291,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mTranslateableViews.remove(mGutsStub);
// We don't handle focus highlight in this view, it's done in background drawable instead
setDefaultFocusHighlightEnabled(false);
+
+ if (NotificationAddXOnHoverToDismiss.isEnabled()) {
+ addDismissButtonTargetStateListener(findViewById(R.id.backgroundNormal));
+ }
}
/**
@@ -3754,7 +3831,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
@Override
public boolean isAboveShelf() {
return (canShowHeadsUp()
- && (mIsPinned || mHeadsupDisappearRunning || (mIsHeadsUp && mAboveShelf)
+ && (mPinnedStatus.isPinned()
+ || mHeadsupDisappearRunning || (mIsHeadsUp && mAboveShelf)
|| mExpandAnimationRunning || mChildIsExpanding));
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
index ffe1b6f88302..a150f7f1f54e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
@@ -59,7 +59,7 @@ import com.android.systemui.statusbar.notification.stack.NotificationChildrenCon
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.notification.stack.ui.view.NotificationRowStatsLogger;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
import com.android.systemui.statusbar.policy.SmartReplyConstants;
import com.android.systemui.statusbar.policy.dagger.RemoteInputViewSubcomponent;
import com.android.systemui.util.time.SystemClock;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
index 77f5717d0e91..11db2fc77170 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
@@ -52,7 +52,7 @@ import com.android.systemui.res.R;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.notification.logging.NotificationPanelLogger;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
import javax.inject.Inject;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
index d0db5145e0ff..34ef63944f14 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
@@ -21,7 +21,9 @@ import static com.android.systemui.util.ColorUtilKt.hexColorString;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Canvas;
+import android.graphics.Path;
import android.graphics.PorterDuff;
+import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.LayerDrawable;
@@ -36,6 +38,7 @@ import com.android.internal.util.ContrastColorUtil;
import com.android.settingslib.Utils;
import com.android.systemui.Dumpable;
import com.android.systemui.res.R;
+import com.android.systemui.statusbar.notification.shared.NotificationAddXOnHoverToDismiss;
import com.android.systemui.util.DrawableDumpKt;
import java.io.PrintWriter;
@@ -44,7 +47,8 @@ import java.util.Arrays;
/**
* A view that can be used for both the dimmed and normal background of an notification.
*/
-public class NotificationBackgroundView extends View implements Dumpable {
+public class NotificationBackgroundView extends View implements Dumpable,
+ ExpandableNotificationRow.DismissButtonTargetVisibilityListener {
private final boolean mDontModifyCorners;
private Drawable mBackground;
@@ -66,6 +70,11 @@ public class NotificationBackgroundView extends View implements Dumpable {
private final ColorStateList mLightColoredStatefulColors;
private final ColorStateList mDarkColoredStatefulColors;
private final int mNormalColor;
+ private final int convexR = 9;
+ private final int concaveR = 22;
+
+ // True only if the dismiss button is visible.
+ private boolean mDrawDismissButtonCutout = false;
public NotificationBackgroundView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -80,6 +89,18 @@ public class NotificationBackgroundView extends View implements Dumpable {
}
@Override
+ public void onTargetVisibilityChanged(boolean targetVisible) {
+ if (NotificationAddXOnHoverToDismiss.isUnexpectedlyInLegacyMode()) {
+ return;
+ }
+
+ if (mDrawDismissButtonCutout != targetVisible) {
+ mDrawDismissButtonCutout = targetVisible;
+ invalidate();
+ }
+ }
+
+ @Override
protected void onDraw(Canvas canvas) {
if (mClipTopAmount + mClipBottomAmount < getActualHeight() || mExpandAnimationRunning) {
canvas.save();
@@ -87,12 +108,87 @@ public class NotificationBackgroundView extends View implements Dumpable {
canvas.clipRect(0, mClipTopAmount, getWidth(),
getActualHeight() - mClipBottomAmount);
}
- draw(canvas, mBackground);
+
+ if (!NotificationAddXOnHoverToDismiss.isEnabled()) {
+ draw(canvas, mBackground);
+ canvas.restore();
+ return;
+ }
+
+ Rect backgroundBounds = null;
+ if (mBackground != null || mDrawDismissButtonCutout) {
+ backgroundBounds = calculateBackgroundBounds();
+ }
+
+ if (mDrawDismissButtonCutout) {
+ canvas.clipPath(calculateDismissButtonCutoutPath(backgroundBounds));
+ }
+
+ if (mBackground != null) {
+ mBackground.setBounds(backgroundBounds);
+ mBackground.draw(canvas);
+ }
+
canvas.restore();
}
}
+ private Path calculateDismissButtonCutoutPath(Rect backgroundBounds) {
+ // TODO(b/365585705): Adapt to RTL after the UX design is finalized.
+
+ NotificationAddXOnHoverToDismiss.isUnexpectedlyInLegacyMode();
+
+ Path path = new Path();
+
+ final int left = backgroundBounds.left;
+ final int right = backgroundBounds.right;
+ final int top = backgroundBounds.top;
+ final int bottom = backgroundBounds.bottom;
+
+ // Generate the path clockwise from the left-top corner.
+ path.moveTo(left, top);
+ path.lineTo(right - 2 * convexR - concaveR, top);
+ path.quadTo(right - convexR - concaveR, top, right - convexR - concaveR,
+ top + convexR);
+ path.quadTo(right - convexR - concaveR, top + convexR + concaveR, right - convexR,
+ top + convexR + concaveR);
+ path.quadTo(right, top + convexR + concaveR, right, top + 2 * convexR + concaveR);
+ path.lineTo(right, bottom);
+ path.lineTo(left, bottom);
+ path.lineTo(left, top);
+
+ return path;
+ }
+
+ private Rect calculateBackgroundBounds() {
+ NotificationAddXOnHoverToDismiss.isUnexpectedlyInLegacyMode();
+
+ int top = 0;
+ int bottom = getActualHeight();
+ if (mBottomIsRounded
+ && mBottomAmountClips
+ && !mExpandAnimationRunning) {
+ bottom -= mClipBottomAmount;
+ }
+ final boolean isRtl = isLayoutRtl();
+ final int width = getWidth();
+ final int actualWidth = getActualWidth();
+
+ int left = isRtl ? width - actualWidth : 0;
+ int right = isRtl ? width : actualWidth;
+
+ if (mExpandAnimationRunning) {
+ // Horizontally center this background view inside of the container
+ left = (int) ((width - actualWidth) / 2.0f);
+ right = (int) (left + actualWidth);
+ }
+
+ return new Rect(left, top, right, bottom);
+ }
+
private void draw(Canvas canvas, Drawable drawable) {
+ NotificationAddXOnHoverToDismiss.assertInLegacyMode();
+
if (drawable != null) {
int top = 0;
int bottom = getActualHeight();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index 41abac1d47f7..6e05e8e8b80e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -54,6 +54,8 @@ import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.notification.ConversationNotificationProcessor;
import com.android.systemui.statusbar.notification.InflationException;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractor;
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel;
import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation;
import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation;
import com.android.systemui.statusbar.notification.row.shared.LockscreenOtpRedaction;
@@ -92,6 +94,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
private final SmartReplyStateInflater mSmartReplyStateInflater;
private final NotifLayoutInflaterFactory.Provider mNotifLayoutInflaterFactoryProvider;
private final HeadsUpStyleProvider mHeadsUpStyleProvider;
+ private final PromotedNotificationContentExtractor mPromotedNotificationContentExtractor;
private final NotificationRowContentBinderLogger mLogger;
@@ -105,6 +108,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
SmartReplyStateInflater smartRepliesInflater,
NotifLayoutInflaterFactory.Provider notifLayoutInflaterFactoryProvider,
HeadsUpStyleProvider headsUpStyleProvider,
+ PromotedNotificationContentExtractor promotedNotificationContentExtractor,
NotificationRowContentBinderLogger logger) {
NotificationRowContentBinderRefactor.assertInLegacyMode();
mRemoteViewCache = remoteViewCache;
@@ -115,6 +119,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
mSmartReplyStateInflater = smartRepliesInflater;
mNotifLayoutInflaterFactoryProvider = notifLayoutInflaterFactoryProvider;
mHeadsUpStyleProvider = headsUpStyleProvider;
+ mPromotedNotificationContentExtractor = promotedNotificationContentExtractor;
mLogger = logger;
}
@@ -165,6 +170,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
mSmartReplyStateInflater,
mNotifLayoutInflaterFactoryProvider,
mHeadsUpStyleProvider,
+ mPromotedNotificationContentExtractor,
mLogger);
if (mInflateSynchronously) {
task.onPostExecute(task.doInBackground());
@@ -913,6 +919,11 @@ public class NotificationContentInflater implements NotificationRowContentBinder
NotificationContentView privateLayout = row.getPrivateLayout();
NotificationContentView publicLayout = row.getPublicLayout();
logger.logAsyncTaskProgress(entry, "finishing");
+
+ if (PromotedNotificationContentModel.featureFlagEnabled()) {
+ entry.setPromotedNotificationContentModel(result.mExtractedPromotedNotificationContent);
+ }
+
boolean setRepliesAndActions = true;
if ((reInflateFlags & FLAG_CONTENT_VIEW_CONTRACTED) != 0) {
if (result.inflatedContentView != null) {
@@ -1123,6 +1134,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
private final SmartReplyStateInflater mSmartRepliesInflater;
private final NotifLayoutInflaterFactory.Provider mNotifLayoutInflaterFactoryProvider;
private final HeadsUpStyleProvider mHeadsUpStyleProvider;
+ private final PromotedNotificationContentExtractor mPromotedNotificationContentExtractor;
private final NotificationRowContentBinderLogger mLogger;
private AsyncInflationTask(
@@ -1142,6 +1154,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
SmartReplyStateInflater smartRepliesInflater,
NotifLayoutInflaterFactory.Provider notifLayoutInflaterFactoryProvider,
HeadsUpStyleProvider headsUpStyleProvider,
+ PromotedNotificationContentExtractor promotedNotificationContentExtractor,
NotificationRowContentBinderLogger logger) {
mEntry = entry;
mRow = row;
@@ -1160,6 +1173,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
mIsMediaInQS = isMediaFlagEnabled;
mNotifLayoutInflaterFactoryProvider = notifLayoutInflaterFactoryProvider;
mHeadsUpStyleProvider = headsUpStyleProvider;
+ mPromotedNotificationContentExtractor = promotedNotificationContentExtractor;
mLogger = logger;
entry.setInflationTask(this);
}
@@ -1276,6 +1290,14 @@ public class NotificationContentInflater implements NotificationRowContentBinder
);
}
+ if (PromotedNotificationContentModel.featureFlagEnabled()) {
+ mLogger.logAsyncTaskProgress(mEntry, "extracting promoted notification content");
+ result.mExtractedPromotedNotificationContent = mPromotedNotificationContentExtractor
+ .extractContent(mEntry, recoveredBuilder);
+ mLogger.logAsyncTaskProgress(mEntry, "extracted promoted notification content: "
+ + result.mExtractedPromotedNotificationContent);
+ }
+
mLogger.logAsyncTaskProgress(mEntry,
"getting row image resolver (on wrong thread!)");
final NotificationInlineImageResolver imageResolver = mRow.getImageResolver();
@@ -1377,6 +1399,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder
@VisibleForTesting
static class InflationProgress {
+ PromotedNotificationContentModel mExtractedPromotedNotificationContent;
+
private RemoteViews newContentView;
private RemoteViews newHeadsUpView;
private RemoteViews newExpandedView;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index 5ff9bc61f3ce..ea508748fada 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -72,7 +72,7 @@ import com.android.systemui.statusbar.notification.collection.render.NotifGutsVi
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.wmshell.BubblesManager;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
index d0c033bb10b0..c7d80e9d03ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.row
import android.annotation.SuppressLint
+import android.app.Flags
import android.app.Notification
import android.content.Context
import android.content.ContextWrapper
@@ -46,6 +47,8 @@ import com.android.systemui.statusbar.NotificationRemoteInputManager
import com.android.systemui.statusbar.notification.ConversationNotificationProcessor
import com.android.systemui.statusbar.notification.InflationException
import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractor
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
import com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_CONTRACTED
import com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_EXPANDED
import com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP
@@ -95,6 +98,7 @@ constructor(
private val smartReplyStateInflater: SmartReplyStateInflater,
private val notifLayoutInflaterFactoryProvider: NotifLayoutInflaterFactory.Provider,
private val headsUpStyleProvider: HeadsUpStyleProvider,
+ private val promotedNotificationContentExtractor: PromotedNotificationContentExtractor,
private val logger: NotificationRowContentBinderLogger,
) : NotificationRowContentBinder {
@@ -147,6 +151,7 @@ constructor(
/* isMediaFlagEnabled = */ smartReplyStateInflater,
notifLayoutInflaterFactoryProvider,
headsUpStyleProvider,
+ promotedNotificationContentExtractor,
logger,
)
if (inflateSynchronously) {
@@ -166,6 +171,7 @@ constructor(
builder: Notification.Builder,
packageContext: Context,
smartRepliesInflater: SmartReplyStateInflater,
+ promotedNotificationContentExtractor: PromotedNotificationContentExtractor,
): InflationProgress {
val systemUIContext = row.context
val result =
@@ -182,6 +188,7 @@ constructor(
notifLayoutInflaterFactoryProvider = notifLayoutInflaterFactoryProvider,
headsUpStyleProvider = headsUpStyleProvider,
conversationProcessor = conversationProcessor,
+ promotedNotificationContentExtractor = promotedNotificationContentExtractor,
logger = logger,
)
inflateSmartReplyViews(
@@ -372,6 +379,7 @@ constructor(
private val smartRepliesInflater: SmartReplyStateInflater,
private val notifLayoutInflaterFactoryProvider: NotifLayoutInflaterFactory.Provider,
private val headsUpStyleProvider: HeadsUpStyleProvider,
+ private val promotedNotificationContentExtractor: PromotedNotificationContentExtractor,
private val logger: NotificationRowContentBinderLogger,
) : AsyncTask<Void, Void, Result<InflationProgress>>(), InflationCallback, InflationTask {
private val context: Context
@@ -442,6 +450,7 @@ constructor(
notifLayoutInflaterFactoryProvider = notifLayoutInflaterFactoryProvider,
headsUpStyleProvider = headsUpStyleProvider,
conversationProcessor = conversationProcessor,
+ promotedNotificationContentExtractor = promotedNotificationContentExtractor,
logger = logger,
)
logger.logAsyncTaskProgress(
@@ -582,6 +591,7 @@ constructor(
@VisibleForTesting val packageContext: Context,
val remoteViews: NewRemoteViews,
val contentModel: NotificationContentModel,
+ val extractedPromotedNotificationContentModel: PromotedNotificationContentModel?,
) {
var inflatedContentView: View? = null
@@ -670,8 +680,23 @@ constructor(
notifLayoutInflaterFactoryProvider: NotifLayoutInflaterFactory.Provider,
headsUpStyleProvider: HeadsUpStyleProvider,
conversationProcessor: ConversationNotificationProcessor,
+ promotedNotificationContentExtractor: PromotedNotificationContentExtractor,
logger: NotificationRowContentBinderLogger,
): InflationProgress {
+ val promoted =
+ if (PromotedNotificationContentModel.featureFlagEnabled()) {
+ logger.logAsyncTaskProgress(entry, "extracting promoted notification content")
+ val extracted =
+ promotedNotificationContentExtractor.extractContent(entry, builder)
+ logger.logAsyncTaskProgress(
+ entry,
+ "extracted promoted notification content: {extracted}",
+ )
+ extracted
+ } else {
+ null
+ }
+
// process conversations and extract the messaging style
val messagingStyle =
if (entry.ranking.isConversation) {
@@ -734,6 +759,7 @@ constructor(
packageContext = packageContext,
remoteViews = remoteViews,
contentModel = contentModel,
+ extractedPromotedNotificationContentModel = promoted,
)
}
@@ -1393,6 +1419,11 @@ constructor(
logger.logAsyncTaskProgress(entry, "finishing")
entry.setContentModel(result.contentModel)
+ if (PromotedNotificationContentModel.featureFlagEnabled()) {
+ entry.promotedNotificationContentModel =
+ result.extractedPromotedNotificationContentModel
+ }
+
result.inflatedSmartReplyState?.let { row.privateLayout.setInflatedSmartReplyState(it) }
setContentViewsFromRemoteViews(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
index b622defbef98..e9eecdd8a26f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
@@ -16,6 +16,9 @@
package com.android.systemui.statusbar.notification.row.wrapper;
+import static android.view.View.GONE;
+import static android.view.View.VISIBLE;
+
import static com.android.systemui.statusbar.notification.TransformState.TRANSFORM_Y;
import android.app.Notification;
@@ -48,6 +51,7 @@ import com.android.systemui.statusbar.notification.Roundable;
import com.android.systemui.statusbar.notification.RoundableState;
import com.android.systemui.statusbar.notification.TransformState;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.shared.NotificationAddXOnHoverToDismiss;
import java.util.Stack;
@@ -115,6 +119,10 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper imple
resolveHeaderViews();
addFeedbackOnClickListener(row);
addCloseButtonOnClickListener(row);
+
+ if (NotificationAddXOnHoverToDismiss.isEnabled()) {
+ mRow.addDismissButtonTargetStateListener(mHoverListener);
+ }
}
@Override
@@ -166,13 +174,34 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper imple
}
}
+ private ExpandableNotificationRow.DismissButtonTargetVisibilityListener mHoverListener = new
+ ExpandableNotificationRow.DismissButtonTargetVisibilityListener() {
+ @Override
+ public void onTargetVisibilityChanged(boolean targetVisible) {
+ NotificationAddXOnHoverToDismiss.isUnexpectedlyInLegacyMode();
+
+ if (mCloseButton != null) {
+ mCloseButton.setVisibility(targetVisible ? VISIBLE : GONE);
+ }
+ }
+ };
+
+ @Override
+ public void setRemoved() {
+ super.setRemoved();
+
+ if (NotificationAddXOnHoverToDismiss.isEnabled()) {
+ mRow.removeDismissButtonTargetStateListener(mHoverListener);
+ }
+ }
+
/**
* Shows the given feedback icon, or hides the icon if null.
*/
@Override
public void setFeedbackIcon(@Nullable FeedbackIcon icon) {
if (mFeedbackIcon != null) {
- mFeedbackIcon.setVisibility(icon != null ? View.VISIBLE : View.GONE);
+ mFeedbackIcon.setVisibility(icon != null ? VISIBLE : GONE);
if (icon != null) {
if (mFeedbackIcon instanceof ImageButton) {
((ImageButton) mFeedbackIcon).setImageResource(icon.getIconRes());
@@ -266,7 +295,7 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper imple
boolean expandable,
View.OnClickListener onClickListener,
boolean requestLayout) {
- mExpandButton.setVisibility(expandable ? View.VISIBLE : View.GONE);
+ mExpandButton.setVisibility(expandable ? VISIBLE : GONE);
mExpandButton.setOnClickListener(expandable ? onClickListener : null);
if (mAltExpandTarget != null) {
mAltExpandTarget.setOnClickListener(expandable ? onClickListener : null);
@@ -294,7 +323,7 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper imple
@Override
public void setRecentlyAudiblyAlerted(boolean audiblyAlerted) {
if (mAudiblyAlertedIcon != null) {
- mAudiblyAlertedIcon.setVisibility(audiblyAlerted ? View.VISIBLE : View.GONE);
+ mAudiblyAlertedIcon.setVisibility(audiblyAlerted ? VISIBLE : GONE);
}
}
@@ -371,6 +400,7 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper imple
((DateTimeView) timeView).setTime(whenMillis);
}
}
+
protected void addTransformedViews(View... views) {
for (View view : views) {
if (view != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationAddXOnHoverToDismiss.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationAddXOnHoverToDismiss.kt
new file mode 100644
index 000000000000..0961874b2276
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationAddXOnHoverToDismiss.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.shared
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the notification dismiss button on hover flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object NotificationAddXOnHoverToDismiss {
+ const val FLAG_NAME = Flags.FLAG_NOTIFICATION_ADD_X_ON_HOVER_TO_DISMISS
+
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
+ @JvmStatic
+ inline val isEnabled
+ get() = Flags.notificationAddXOnHoverToDismiss()
+
+ @JvmStatic
+ inline fun isUnexpectedlyInLegacyMode() =
+ RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+ @JvmStatic
+ inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index 777142194221..ad3611796d62 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -39,7 +39,7 @@ import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.BypassController;
import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.SectionProvider;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.policy.AvalancheController;
+import com.android.systemui.statusbar.notification.headsup.AvalancheController;
import java.io.PrintWriter;
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 bddf6dffe7c0..223475e6e3b8 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
@@ -99,7 +99,7 @@ import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.ColorUpdateLogger;
import com.android.systemui.statusbar.notification.FakeShadowView;
-import com.android.systemui.statusbar.notification.HeadsUpTouchHelper;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpTouchHelper;
import com.android.systemui.statusbar.notification.LaunchAnimationParameters;
import com.android.systemui.statusbar.notification.NotificationTransitionAnimatorController;
import com.android.systemui.statusbar.notification.NotificationUtils;
@@ -127,7 +127,7 @@ import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrol
import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView;
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
-import com.android.systemui.statusbar.policy.HeadsUpUtil;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpUtil;
import com.android.systemui.statusbar.policy.ScrollAdapter;
import com.android.systemui.statusbar.policy.SplitShadeStateController;
import com.android.systemui.util.Assert;
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 3d7501deafc7..e89645d7cb94 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
@@ -98,9 +98,9 @@ import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.ColorUpdateLogger;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
-import com.android.systemui.statusbar.notification.HeadsUpNotificationViewControllerEmptyImpl;
-import com.android.systemui.statusbar.notification.HeadsUpTouchHelper;
-import com.android.systemui.statusbar.notification.HeadsUpTouchHelper.HeadsUpNotificationViewController;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpNotificationViewControllerEmptyImpl;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpTouchHelper;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpTouchHelper.HeadsUpNotificationViewController;
import com.android.systemui.statusbar.notification.LaunchAnimationParameters;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
@@ -138,8 +138,8 @@ import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.OnHeadsUpChangedListener;
import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController;
import com.android.systemui.statusbar.policy.SplitShadeStateController;
import com.android.systemui.statusbar.policy.ZenModeController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java
index ef145578bbac..b2ffa4aa8233 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java
@@ -33,7 +33,7 @@ import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.NotificationFadeAware.FadeOptimizedNotification;
import com.android.systemui.statusbar.notification.PropertyAnimator;
import com.android.systemui.statusbar.notification.row.ExpandableView;
-import com.android.systemui.statusbar.policy.HeadsUpUtil;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpUtil;
import java.io.PrintWriter;
import java.lang.reflect.Field;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index 5209d0f1551e..7f95fb072ede 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -66,7 +66,7 @@ import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index db294934c9ed..c6af3280eef1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -221,7 +221,7 @@ import com.android.systemui.statusbar.policy.ConfigurationController.Configurati
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
import com.android.systemui.statusbar.policy.ExtensionController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
import com.android.systemui.statusbar.window.StatusBarWindowControllerStore;
@@ -401,7 +401,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
private final KeyguardBypassController mKeyguardBypassController;
private final KeyguardStateController mKeyguardStateController;
private final HeadsUpManager mHeadsUpManager;
- private final StatusBarTouchableRegionManager mStatusBarTouchableRegionManager;
+ private final ShadeTouchableRegionManager mShadeTouchableRegionManager;
private final FalsingCollector mFalsingCollector;
private final FalsingManager mFalsingManager;
private final BroadcastDispatcher mBroadcastDispatcher;
@@ -681,7 +681,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
KeyguardIndicationController keyguardIndicationController,
DemoModeController demoModeController,
Lazy<NotificationShadeDepthController> notificationShadeDepthControllerLazy,
- StatusBarTouchableRegionManager statusBarTouchableRegionManager,
+ ShadeTouchableRegionManager shadeTouchableRegionManager,
BrightnessSliderController.Factory brightnessSliderFactory,
ScreenOffAnimationController screenOffAnimationController,
WallpaperController wallpaperController,
@@ -724,7 +724,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
mHeadsUpManager = headsUpManager;
mBackActionInteractor = backActionInteractor;
mKeyguardIndicationController = keyguardIndicationController;
- mStatusBarTouchableRegionManager = statusBarTouchableRegionManager;
+ mShadeTouchableRegionManager = shadeTouchableRegionManager;
mFalsingCollector = falsingCollector;
mFalsingManager = falsingManager;
mBroadcastDispatcher = broadcastDispatcher;
@@ -1232,7 +1232,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
mStatusBarInitializer.initializeStatusBar();
}
- mStatusBarTouchableRegionManager.setup(getNotificationShadeWindowView());
+ mShadeTouchableRegionManager.setup(getNotificationShadeWindowView());
if (!StatusBarConnectedDisplays.isEnabled()) {
createNavigationBar(result);
@@ -1856,10 +1856,10 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
pw.println(" mHeadsUpManager: null");
}
- if (mStatusBarTouchableRegionManager != null) {
- mStatusBarTouchableRegionManager.dump(pw, args);
+ if (mShadeTouchableRegionManager != null) {
+ mShadeTouchableRegionManager.dump(pw, args);
} else {
- pw.println(" mStatusBarTouchableRegionManager: null");
+ pw.println(" mShadeTouchableRegionManager: null");
}
if (mLightBarController != null) {
@@ -2566,7 +2566,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
dismissVolumeDialog();
mWakeUpCoordinator.setFullyAwake(false);
mKeyguardBypassController.onStartedGoingToSleep();
- mStatusBarTouchableRegionManager.updateTouchableRegion();
+ mShadeTouchableRegionManager.updateTouchableRegion();
// The unlocked screen off and fold to aod animations might use our LightRevealScrim -
// we need to be expanded for it to be visible.
@@ -2655,7 +2655,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
// once we fully woke up.
updateRevealEffect(true /* wakingUp */);
updateNotificationPanelTouchState();
- mStatusBarTouchableRegionManager.updateTouchableRegion();
+ mShadeTouchableRegionManager.updateTouchableRegion();
// If we are waking up during the screen off animation, we should undo making the
// expanded visible (we did that so the LightRevealScrim would be visible).
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
index ec92990441ce..57e26d708f26 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
@@ -54,8 +54,8 @@ import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.OnHeadsUpChangedListener;
import com.android.systemui.util.Assert;
import com.android.systemui.util.CopyOnLoopListenerSet;
import com.android.systemui.util.IListenerSet;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
index 8de03d83d7af..b9639a7a90f2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
@@ -47,9 +47,9 @@ import com.android.systemui.statusbar.notification.stack.NotificationRoundnessMa
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.phone.fragment.dagger.HomeStatusBarScope;
import com.android.systemui.statusbar.policy.Clock;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
+import com.android.systemui.statusbar.notification.headsup.OnHeadsUpChangedListener;
import com.android.systemui.util.ViewController;
import java.util.ArrayList;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.kt
index a6374a66806b..cd9b9d244e0e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.phone
+import android.content.Context
import android.view.WindowInsetsController
import com.android.internal.colorextraction.ColorExtractor
import com.android.internal.view.AppearanceRegion
@@ -64,4 +65,8 @@ interface LightBarController : CoreStartable {
scrimBehindAlpha: Float,
scrimInFrontColor: ColorExtractor.GradientColors,
)
+
+ fun interface Factory {
+ fun create(context: Context): LightBarController
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarControllerImpl.java
index ccb9a119d92f..1a4f3ca5b07f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarControllerImpl.java
@@ -22,6 +22,7 @@ import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT;
import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_TRANSPARENT;
+import android.content.Context;
import android.graphics.Rect;
import android.util.Log;
import android.view.Display;
@@ -34,12 +35,15 @@ import androidx.annotation.Nullable;
import com.android.internal.colorextraction.ColorExtractor.GradientColors;
import com.android.internal.view.AppearanceRegion;
+import com.android.systemui.dagger.qualifiers.Application;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.statusbar.data.model.StatusBarAppearance;
+import com.android.systemui.statusbar.data.repository.DarkIconDispatcherStore;
import com.android.systemui.statusbar.data.repository.StatusBarModePerDisplayRepository;
+import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.util.Compile;
import com.android.systemui.util.kotlin.JavaAdapterKt;
@@ -55,6 +59,8 @@ import kotlinx.coroutines.CoroutineScope;
import java.io.PrintWriter;
import java.util.ArrayList;
+import javax.inject.Inject;
+
/**
* Controls how light status bar flag applies to the icons.
*/
@@ -67,6 +73,7 @@ public class LightBarControllerImpl implements
private static final float NAV_BAR_INVERSION_SCRIM_ALPHA_THRESHOLD = 0.1f;
+ private final int mDisplayId;
private final CoroutineScope mCoroutineScope;
private final SysuiDarkIconDispatcher mStatusBarIconController;
private final BatteryController mBatteryController;
@@ -140,6 +147,7 @@ public class LightBarControllerImpl implements
DumpManager dumpManager,
@Main CoroutineContext mainContext,
BiometricUnlockController biometricUnlockController) {
+ mDisplayId = displayId;
mCoroutineScope = coroutineScope;
mStatusBarIconController = (SysuiDarkIconDispatcher) darkIconDispatcher;
mBatteryController = batteryController;
@@ -155,7 +163,12 @@ public class LightBarControllerImpl implements
@Override
public void start() {
- mDumpManager.registerCriticalDumpable(mDumpableName, this);
+ if (mDisplayId == Display.DEFAULT_DISPLAY) {
+ // Can only register on default display, because NavigationBar creates its own instance
+ // as well as PerDisplayStore.
+ // TODO: b/380394368 - make sure there is only one instance per display.
+ mDumpManager.registerCriticalDumpable(mDumpableName, this);
+ }
mBatteryController.addCallback(this);
mNavigationMode = mNavModeController.addListener(mNavigationModeListener);
JavaAdapterKt.collectFlow(
@@ -490,4 +503,36 @@ public class LightBarControllerImpl implements
DarkIconDispatcher darkIconDispatcher,
StatusBarModePerDisplayRepository statusBarModePerDisplayRepository);
}
+
+ public static class LegacyFactory implements LightBarController.Factory {
+
+ private final Factory mFactory;
+ private final CoroutineScope mApplicationScope;
+ private final DarkIconDispatcherStore mDarkIconDispatcherStore;
+ private final StatusBarModeRepositoryStore mStatusBarModeRepositoryStore;
+
+ @Inject
+ public LegacyFactory(
+ LightBarControllerImpl.Factory factory,
+ @Application CoroutineScope applicationScope,
+ DarkIconDispatcherStore darkIconDispatcherStore,
+ StatusBarModeRepositoryStore statusBarModeRepositoryStore) {
+ mFactory = factory;
+ mApplicationScope = applicationScope;
+ mDarkIconDispatcherStore = darkIconDispatcherStore;
+ mStatusBarModeRepositoryStore = statusBarModeRepositoryStore;
+ }
+
+ @NonNull
+ @Override
+ public LightBarController create(@NonNull Context context) {
+ // TODO: b/380394368 - Make sure correct per display instances are used.
+ return mFactory.create(
+ context.getDisplayId(),
+ mApplicationScope,
+ mDarkIconDispatcherStore.getDefaultDisplay(),
+ mStatusBarModeRepositoryStore.getDefaultDisplay()
+ );
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeTouchableRegionManager.java
index d2c2003112f6..bea8397fed62 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeTouchableRegionManager.java
@@ -47,8 +47,8 @@ import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.OnHeadsUpChangedListener;
import com.android.systemui.util.kotlin.JavaAdapter;
import java.io.PrintWriter;
@@ -62,7 +62,7 @@ import javax.inject.Provider;
* of HeadsUpNotifications.
*/
@SysUISingleton
-public final class StatusBarTouchableRegionManager implements Dumpable {
+public final class ShadeTouchableRegionManager implements Dumpable {
private static final String TAG = "TouchableRegionManager";
private final Context mContext;
@@ -90,7 +90,7 @@ public final class StatusBarTouchableRegionManager implements Dumpable {
private final OnComputeInternalInsetsListener mOnComputeInternalInsetsListener;
@Inject
- public StatusBarTouchableRegionManager(
+ public ShadeTouchableRegionManager(
Context context,
NotificationShadeWindowController notificationShadeWindowController,
ConfigurationController configurationController,
@@ -165,7 +165,7 @@ public final class StatusBarTouchableRegionManager implements Dumpable {
@Override
public void dump(PrintWriter pw, String[] args) {
- pw.println("StatusBarTouchableRegionManager state:");
+ pw.println("ShadeTouchableRegionManager state:");
pw.print(" mTouchableRegion=");
pw.println(mTouchableRegion);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index af98311c937f..e33baf7c33ae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -79,8 +79,8 @@ import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyS
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowDragController;
import com.android.systemui.statusbar.notification.row.OnUserInteractionCallback;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.statusbar.policy.HeadsUpUtil;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpUtil;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.wmshell.BubblesManager;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index 79ea59c27090..b3cc047251ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -69,7 +69,7 @@ import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import java.util.Set;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt
index ee8c1ae6c5f9..42b9d5b12afc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt
@@ -207,5 +207,19 @@ interface StatusBarPhoneModule {
singleDisplayLazy.get()
}
}
+
+ @Provides
+ @SysUISingleton
+ @IntoMap
+ @ClassKey(AutoHideControllerStore::class)
+ fun storeAsCoreStartable(
+ multiDisplayLazy: Lazy<MultiDisplayAutoHideControllerStore>
+ ): CoreStartable {
+ return if (StatusBarConnectedDisplays.isEnabled) {
+ multiDisplayLazy.get()
+ } else {
+ CoreStartable.NOP
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
index aac2cd1755d0..78926c78a368 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
@@ -25,6 +25,7 @@ import android.app.UidObserver
import android.content.Context
import android.view.View
import androidx.annotation.VisibleForTesting
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.CoreStartable
import com.android.systemui.Dumpable
@@ -58,7 +59,6 @@ import java.io.PrintWriter
import java.util.concurrent.Executor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
-import com.android.app.tracing.coroutines.launchTraced as launch
/** A controller to handle the ongoing call chip in the collapsed status bar. */
@SysUISingleton
@@ -122,9 +122,9 @@ constructor(
entry.sbn.uid,
entry.sbn.notification.extras.getInt(
Notification.EXTRA_CALL_TYPE,
- -1
+ -1,
) == CALL_TYPE_ONGOING,
- statusBarSwipedAway = callNotificationInfo?.statusBarSwipedAway ?: false
+ statusBarSwipedAway = callNotificationInfo?.statusBarSwipedAway ?: false,
)
if (newOngoingCallInfo == callNotificationInfo) {
return
@@ -236,7 +236,7 @@ constructor(
bool1 = Flags.statusBarCallChipNotificationIcon()
bool2 = currentInfo.notificationIconView != null
},
- { "Creating OngoingCallModel.InCall. notifIconFlag=$bool1 hasIcon=$bool2" }
+ { "Creating OngoingCallModel.InCall. notifIconFlag=$bool1 hasIcon=$bool2" },
)
val icon =
if (Flags.statusBarCallChipNotificationIcon()) {
@@ -288,7 +288,7 @@ constructor(
str1 = notifModel.callType.name
bool1 = notifModel.statusBarChipIconView != null
},
- { "NotifInteractorCallModel: key=$str1 when=$long1 callType=$str2 hasIcon=$bool1" }
+ { "NotifInteractorCallModel: key=$str1 when=$long1 callType=$str2 hasIcon=$bool1" },
)
val newOngoingCallInfo =
@@ -299,7 +299,7 @@ constructor(
notifModel.contentIntent,
notifModel.uid,
isOngoing = true,
- statusBarSwipedAway = callNotificationInfo?.statusBarSwipedAway ?: false
+ statusBarSwipedAway = callNotificationInfo?.statusBarSwipedAway ?: false,
)
if (newOngoingCallInfo == callNotificationInfo) {
return
@@ -378,7 +378,7 @@ constructor(
ActivityTransitionAnimator.Controller.fromView(
backgroundView,
InteractionJankMonitor.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP,
- )
+ ),
)
}
}
@@ -455,7 +455,7 @@ constructor(
/** True if the call is currently ongoing (as opposed to incoming, screening, etc.). */
val isOngoing: Boolean,
/** True if the user has swiped away the status bar while in this phone call. */
- val statusBarSwipedAway: Boolean
+ val statusBarSwipedAway: Boolean,
) {
/**
* Returns true if the notification information has a valid call start time. See
@@ -472,6 +472,9 @@ constructor(
/**
* Observer to tell us when the app that posted the ongoing call notification is visible so that
* we don't show the call chip at the same time (since the timers could be out-of-sync).
+ *
+ * For a more recommended architecture implementation, see
+ * [com.android.systemui.activity.data.repository.ActivityManagerRepository].
*/
inner class CallAppUidObserver : UidObserver() {
/** True if the application managing the call is visible to the user. */
@@ -512,7 +515,7 @@ constructor(
uidObserver,
ActivityManager.UID_OBSERVER_PROCSTATE,
ActivityManager.PROCESS_STATE_UNKNOWN,
- context.opPackageName
+ context.opPackageName,
)
isRegistered = true
} catch (se: SecurityException) {
@@ -537,7 +540,7 @@ constructor(
uid: Int,
procState: Int,
procStateSeq: Long,
- capability: Int
+ capability: Int,
) {
val currentCallAppUid = callAppUid ?: return
if (uid != currentCallAppUid) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt
index 616992eb0865..56c9e9abbc36 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt
@@ -41,8 +41,8 @@ import android.view.ViewGroup
import android.view.accessibility.AccessibilityNodeInfo
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction
import android.widget.Button
-import com.android.systemui.res.R
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.res.R
import com.android.systemui.shared.system.ActivityManagerWrapper
import com.android.systemui.shared.system.DevicePolicyManagerWrapper
import com.android.systemui.shared.system.PackageManagerWrapper
@@ -50,6 +50,7 @@ import com.android.systemui.statusbar.NotificationRemoteInputManager
import com.android.systemui.statusbar.NotificationUiAdjustment
import com.android.systemui.statusbar.SmartReplyController
import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
import com.android.systemui.statusbar.notification.logging.NotificationLogger
import com.android.systemui.statusbar.phone.KeyguardDismissUtil
import com.android.systemui.statusbar.policy.InflatedSmartReplyState.SuppressedActions
@@ -63,40 +64,42 @@ import java.util.concurrent.TimeUnit
import javax.inject.Inject
import kotlin.system.measureTimeMillis
-
/** Returns whether we should show the smart reply view and its smart suggestions. */
fun shouldShowSmartReplyView(
entry: NotificationEntry,
- smartReplyState: InflatedSmartReplyState
+ smartReplyState: InflatedSmartReplyState,
): Boolean {
- if (smartReplyState.smartReplies == null &&
- smartReplyState.smartActions == null) {
+ if (smartReplyState.smartReplies == null && smartReplyState.smartActions == null) {
// There are no smart replies and no smart actions.
return false
}
// If we are showing the spinner we don't want to add the buttons.
- val showingSpinner = entry.sbn.notification.extras
- .getBoolean(Notification.EXTRA_SHOW_REMOTE_INPUT_SPINNER, false)
+ val showingSpinner =
+ entry.sbn.notification.extras.getBoolean(
+ Notification.EXTRA_SHOW_REMOTE_INPUT_SPINNER,
+ false,
+ )
if (showingSpinner) {
return false
}
// If we are keeping the notification around while sending we don't want to add the buttons.
- return !entry.sbn.notification.extras
- .getBoolean(Notification.EXTRA_HIDE_SMART_REPLIES, false)
+ return !entry.sbn.notification.extras.getBoolean(Notification.EXTRA_HIDE_SMART_REPLIES, false)
}
/** Determines if two [InflatedSmartReplyState] are visually similar. */
fun areSuggestionsSimilar(
left: InflatedSmartReplyState?,
- right: InflatedSmartReplyState?
-): Boolean = when {
- left === right -> true
- left == null || right == null -> false
- left.hasPhishingAction != right.hasPhishingAction -> false
- left.smartRepliesList != right.smartRepliesList -> false
- left.suppressedActionIndices != right.suppressedActionIndices -> false
- else -> !NotificationUiAdjustment.areDifferent(left.smartActionsList, right.smartActionsList)
-}
+ right: InflatedSmartReplyState?,
+): Boolean =
+ when {
+ left === right -> true
+ left == null || right == null -> false
+ left.hasPhishingAction != right.hasPhishingAction -> false
+ left.smartRepliesList != right.smartRepliesList -> false
+ left.suppressedActionIndices != right.suppressedActionIndices -> false
+ else ->
+ !NotificationUiAdjustment.areDifferent(left.smartActionsList, right.smartActionsList)
+ }
interface SmartReplyStateInflater {
fun inflateSmartReplyState(entry: NotificationEntry): InflatedSmartReplyState
@@ -106,181 +109,211 @@ interface SmartReplyStateInflater {
notifPackageContext: Context,
entry: NotificationEntry,
existingSmartReplyState: InflatedSmartReplyState?,
- newSmartReplyState: InflatedSmartReplyState
+ newSmartReplyState: InflatedSmartReplyState,
): InflatedSmartReplyViewHolder
}
-/*internal*/ class SmartReplyStateInflaterImpl @Inject constructor(
+/*internal*/ class SmartReplyStateInflaterImpl
+@Inject
+constructor(
private val constants: SmartReplyConstants,
private val activityManagerWrapper: ActivityManagerWrapper,
private val packageManagerWrapper: PackageManagerWrapper,
private val devicePolicyManagerWrapper: DevicePolicyManagerWrapper,
private val smartRepliesInflater: SmartReplyInflater,
- private val smartActionsInflater: SmartActionInflater
+ private val smartActionsInflater: SmartActionInflater,
) : SmartReplyStateInflater {
override fun inflateSmartReplyState(entry: NotificationEntry): InflatedSmartReplyState =
- chooseSmartRepliesAndActions(entry)
+ chooseSmartRepliesAndActions(entry)
override fun inflateSmartReplyViewHolder(
sysuiContext: Context,
notifPackageContext: Context,
entry: NotificationEntry,
existingSmartReplyState: InflatedSmartReplyState?,
- newSmartReplyState: InflatedSmartReplyState
+ newSmartReplyState: InflatedSmartReplyState,
): InflatedSmartReplyViewHolder {
if (!shouldShowSmartReplyView(entry, newSmartReplyState)) {
return InflatedSmartReplyViewHolder(
- null /* smartReplyView */,
- null /* smartSuggestionButtons */)
+ null /* smartReplyView */,
+ null, /* smartSuggestionButtons */
+ )
}
// Only block clicks if the smart buttons are different from the previous set - to avoid
// scenarios where a user incorrectly cannot click smart buttons because the
// notification is updated.
val delayOnClickListener =
- !areSuggestionsSimilar(existingSmartReplyState, newSmartReplyState)
+ !areSuggestionsSimilar(existingSmartReplyState, newSmartReplyState)
val smartReplyView = SmartReplyView.inflate(sysuiContext, constants)
val smartReplies = newSmartReplyState.smartReplies
smartReplyView.setSmartRepliesGeneratedByAssistant(smartReplies?.fromAssistant ?: false)
- val smartReplyButtons = smartReplies?.let {
- smartReplies.choices.asSequence().mapIndexed { index, choice ->
- smartRepliesInflater.inflateReplyButton(
+ val smartReplyButtons =
+ smartReplies?.let {
+ smartReplies.choices.asSequence().mapIndexed { index, choice ->
+ smartRepliesInflater.inflateReplyButton(
smartReplyView,
entry,
smartReplies,
index,
choice,
- delayOnClickListener)
- }
- } ?: emptySequence()
+ delayOnClickListener,
+ )
+ }
+ } ?: emptySequence()
- val smartActionButtons = newSmartReplyState.smartActions?.let { smartActions ->
- val themedPackageContext =
+ val smartActionButtons =
+ newSmartReplyState.smartActions?.let { smartActions ->
+ val themedPackageContext =
ContextThemeWrapper(notifPackageContext, sysuiContext.theme)
- smartActions.actions.asSequence()
+ smartActions.actions
+ .asSequence()
.filter { it.actionIntent != null }
.mapIndexed { index, action ->
smartActionsInflater.inflateActionButton(
- smartReplyView,
- entry,
- smartActions,
- index,
- action,
- delayOnClickListener,
- themedPackageContext)
+ smartReplyView,
+ entry,
+ smartActions,
+ index,
+ action,
+ delayOnClickListener,
+ themedPackageContext,
+ )
}
- } ?: emptySequence()
+ } ?: emptySequence()
return InflatedSmartReplyViewHolder(
- smartReplyView,
- (smartReplyButtons + smartActionButtons).toList())
+ smartReplyView,
+ (smartReplyButtons + smartActionButtons).toList(),
+ )
}
/**
* Chose what smart replies and smart actions to display. App generated suggestions take
- * precedence. So if the app provides any smart replies, we don't show any
- * replies or actions generated by the NotificationAssistantService (NAS), and if the app
- * provides any smart actions we also don't show any NAS-generated replies or actions.
+ * precedence. So if the app provides any smart replies, we don't show any replies or actions
+ * generated by the NotificationAssistantService (NAS), and if the app provides any smart
+ * actions we also don't show any NAS-generated replies or actions.
*/
fun chooseSmartRepliesAndActions(entry: NotificationEntry): InflatedSmartReplyState {
val notification = entry.sbn.notification
val remoteInputActionPair = notification.findRemoteInputActionPair(false /* freeform */)
val freeformRemoteInputActionPair =
- notification.findRemoteInputActionPair(true /* freeform */)
+ notification.findRemoteInputActionPair(true /* freeform */)
if (!constants.isEnabled) {
if (DEBUG) {
- Log.d(TAG, "Smart suggestions not enabled, not adding suggestions for " +
- entry.sbn.key)
+ Log.d(
+ TAG,
+ "Smart suggestions not enabled, not adding suggestions for " + entry.sbn.key,
+ )
}
return InflatedSmartReplyState(null, null, null, false)
}
// Only use smart replies from the app if they target P or above. We have this check because
// the smart reply API has been used for other things (Wearables) in the past. The API to
// add smart actions is new in Q so it doesn't require a target-sdk check.
- val enableAppGeneratedSmartReplies = (!constants.requiresTargetingP() ||
- entry.targetSdk >= Build.VERSION_CODES.P)
+ val enableAppGeneratedSmartReplies =
+ (!constants.requiresTargetingP() || entry.targetSdk >= Build.VERSION_CODES.P)
val appGeneratedSmartActions = notification.contextualActions
- var smartReplies: SmartReplies? = when {
- enableAppGeneratedSmartReplies -> remoteInputActionPair?.let { pair ->
- pair.second.actionIntent?.let { actionIntent ->
- if (pair.first.choices?.isNotEmpty() == true)
- SmartReplies(
- pair.first.choices.asList(),
- pair.first,
- actionIntent,
- false /* fromAssistant */)
- else null
- }
+ var smartReplies: SmartReplies? =
+ when {
+ enableAppGeneratedSmartReplies ->
+ remoteInputActionPair?.let { pair ->
+ pair.second.actionIntent?.let { actionIntent ->
+ if (pair.first.choices?.isNotEmpty() == true)
+ SmartReplies(
+ pair.first.choices.asList(),
+ pair.first,
+ actionIntent,
+ false, /* fromAssistant */
+ )
+ else null
+ }
+ }
+ else -> null
+ }
+ var smartActions: SmartActions? =
+ when {
+ appGeneratedSmartActions.isNotEmpty() ->
+ SmartActions(appGeneratedSmartActions, false /* fromAssistant */)
+ else -> null
}
- else -> null
- }
- var smartActions: SmartActions? = when {
- appGeneratedSmartActions.isNotEmpty() ->
- SmartActions(appGeneratedSmartActions, false /* fromAssistant */)
- else -> null
- }
// Apps didn't provide any smart replies / actions, use those from NAS (if any).
if (smartReplies == null && smartActions == null) {
val entryReplies = entry.smartReplies
val entryActions = entry.smartActions
- if (entryReplies.isNotEmpty() &&
+ if (
+ entryReplies.isNotEmpty() &&
freeformRemoteInputActionPair != null &&
freeformRemoteInputActionPair.second.allowGeneratedReplies &&
- freeformRemoteInputActionPair.second.actionIntent != null) {
- smartReplies = SmartReplies(
+ freeformRemoteInputActionPair.second.actionIntent != null
+ ) {
+ smartReplies =
+ SmartReplies(
entryReplies,
freeformRemoteInputActionPair.first,
freeformRemoteInputActionPair.second.actionIntent,
- true /* fromAssistant */)
+ true, /* fromAssistant */
+ )
}
- if (entryActions.isNotEmpty() &&
- notification.allowSystemGeneratedContextualActions) {
- val systemGeneratedActions: List<Notification.Action> = when {
- activityManagerWrapper.isLockTaskKioskModeActive ->
- // Filter actions if we're in kiosk-mode - we don't care about screen
- // pinning mode, since notifications aren't shown there anyway.
- filterAllowlistedLockTaskApps(entryActions)
- else -> entryActions
- }
+ if (entryActions.isNotEmpty() && notification.allowSystemGeneratedContextualActions) {
+ val systemGeneratedActions: List<Notification.Action> =
+ when {
+ activityManagerWrapper.isLockTaskKioskModeActive ->
+ // Filter actions if we're in kiosk-mode - we don't care about screen
+ // pinning mode, since notifications aren't shown there anyway.
+ filterAllowlistedLockTaskApps(entryActions)
+ else -> entryActions
+ }
smartActions = SmartActions(systemGeneratedActions, true /* fromAssistant */)
}
}
- val hasPhishingAction = smartActions?.actions?.any {
- it.isContextual && it.semanticAction ==
- Notification.Action.SEMANTIC_ACTION_CONVERSATION_IS_PHISHING
- } ?: false
+ val hasPhishingAction =
+ smartActions?.actions?.any {
+ it.isContextual &&
+ it.semanticAction ==
+ Notification.Action.SEMANTIC_ACTION_CONVERSATION_IS_PHISHING
+ } ?: false
var suppressedActions: SuppressedActions? = null
if (hasPhishingAction) {
// If there is a phishing action, calculate the indices of the actions with RemoteInput
// as those need to be hidden from the view.
- val suppressedActionIndices = notification.actions.mapIndexedNotNull { index, action ->
- if (action.remoteInputs?.isNotEmpty() == true) index else null
- }
+ val suppressedActionIndices =
+ notification.actions.mapIndexedNotNull { index, action ->
+ if (action.remoteInputs?.isNotEmpty() == true) index else null
+ }
suppressedActions = SuppressedActions(suppressedActionIndices)
}
- return InflatedSmartReplyState(smartReplies, smartActions, suppressedActions,
- hasPhishingAction)
+ return InflatedSmartReplyState(
+ smartReplies,
+ smartActions,
+ suppressedActions,
+ hasPhishingAction,
+ )
}
/**
- * Filter actions so that only actions pointing to allowlisted apps are permitted.
- * This filtering is only meaningful when in lock-task mode.
+ * Filter actions so that only actions pointing to allowlisted apps are permitted. This
+ * filtering is only meaningful when in lock-task mode.
*/
private fun filterAllowlistedLockTaskApps(
actions: List<Notification.Action>
- ): List<Notification.Action> = actions.filter { action ->
- // Only allow actions that are explicit (implicit intents are not handled in lock-task
- // mode), and link to allowlisted apps.
- action.actionIntent?.intent?.let { intent ->
- packageManagerWrapper.resolveActivity(intent, 0 /* flags */)
- }?.let { resolveInfo ->
- devicePolicyManagerWrapper.isLockTaskPermitted(resolveInfo.activityInfo.packageName)
- } ?: false
- }
+ ): List<Notification.Action> =
+ actions.filter { action ->
+ // Only allow actions that are explicit (implicit intents are not handled in lock-task
+ // mode), and link to allowlisted apps.
+ action.actionIntent
+ ?.intent
+ ?.let { intent -> packageManagerWrapper.resolveActivity(intent, 0 /* flags */) }
+ ?.let { resolveInfo ->
+ devicePolicyManagerWrapper.isLockTaskPermitted(
+ resolveInfo.activityInfo.packageName
+ )
+ } ?: false
+ }
}
interface SmartActionInflater {
@@ -291,7 +324,7 @@ interface SmartActionInflater {
actionIndex: Int,
action: Notification.Action,
delayOnClickListener: Boolean,
- packageContext: Context
+ packageContext: Context,
): Button
}
@@ -310,28 +343,32 @@ private fun loadIconDrawableWithTimeout(
val bitmap: Bitmap?
val durationMillis = measureTimeMillis {
val source = ImageDecoder.createSource(packageContext.contentResolver, icon.uri)
- bitmap = ImageDecoder.decodeBitmap(source) { decoder, _, _ ->
- decoder.setTargetSize(targetSize, targetSize)
- decoder.allocator = ImageDecoder.ALLOCATOR_DEFAULT
- }
+ bitmap =
+ ImageDecoder.decodeBitmap(source) { decoder, _, _ ->
+ decoder.setTargetSize(targetSize, targetSize)
+ decoder.allocator = ImageDecoder.ALLOCATOR_DEFAULT
+ }
}
if (durationMillis > ICON_TASK_TIMEOUT_MS) {
Log.w(TAG, "Loading $icon took ${durationMillis / 1000f} sec")
}
checkNotNull(bitmap) { "ImageDecoder.decodeBitmap() returned null" }
}
- val bitmap = runCatching {
- iconTaskThreadPool.execute(bitmapTask)
- bitmapTask.get(ICON_TASK_TIMEOUT_MS, TimeUnit.MILLISECONDS)
- }.getOrElse { ex ->
- Log.e(TAG, "Failed to load $icon: $ex")
- bitmapTask.cancel(true)
- return null
- }
+ val bitmap =
+ runCatching {
+ iconTaskThreadPool.execute(bitmapTask)
+ bitmapTask.get(ICON_TASK_TIMEOUT_MS, TimeUnit.MILLISECONDS)
+ }
+ .getOrElse { ex ->
+ Log.e(TAG, "Failed to load $icon: $ex")
+ bitmapTask.cancel(true)
+ return null
+ }
// TODO(b/288561520): rewrite Icon so that we don't need to duplicate this logic
val bitmapDrawable = BitmapDrawable(packageContext.resources, bitmap)
- val result = if (icon.type == Icon.TYPE_URI_ADAPTIVE_BITMAP)
- AdaptiveIconDrawable(null, bitmapDrawable) else bitmapDrawable
+ val result =
+ if (icon.type == Icon.TYPE_URI_ADAPTIVE_BITMAP) AdaptiveIconDrawable(null, bitmapDrawable)
+ else bitmapDrawable
if (icon.hasTint()) {
result.mutate()
result.setTintList(icon.tintList)
@@ -340,11 +377,13 @@ private fun loadIconDrawableWithTimeout(
return result
}
-/* internal */ class SmartActionInflaterImpl @Inject constructor(
+/* internal */ class SmartActionInflaterImpl
+@Inject
+constructor(
private val constants: SmartReplyConstants,
private val activityStarter: ActivityStarter,
private val smartReplyController: SmartReplyController,
- private val headsUpManager: HeadsUpManager
+ private val headsUpManager: HeadsUpManager,
) : SmartActionInflater {
override fun inflateActionButton(
@@ -354,17 +393,18 @@ private fun loadIconDrawableWithTimeout(
actionIndex: Int,
action: Notification.Action,
delayOnClickListener: Boolean,
- packageContext: Context
+ packageContext: Context,
): Button =
- (LayoutInflater.from(parent.context)
- .inflate(R.layout.smart_action_button, parent, false) as Button
- ).apply {
+ (LayoutInflater.from(parent.context).inflate(R.layout.smart_action_button, parent, false)
+ as Button)
+ .apply {
text = action.title
- // We received the Icon from the application - so use the Context of the application to
+ // We received the Icon from the application - so use the Context of the application
+ // to
// reference icon resources.
- val newIconSize = context.resources
- .getDimensionPixelSize(R.dimen.smart_action_button_icon_size)
+ val newIconSize =
+ context.resources.getDimensionPixelSize(R.dimen.smart_action_button_icon_size)
val iconDrawable =
loadIconDrawableWithTimeout(action.getIcon(), packageContext, newIconSize)
?: GradientDrawable()
@@ -372,13 +412,15 @@ private fun loadIconDrawableWithTimeout(
// Add the action icon to the Smart Action button.
setCompoundDrawablesRelative(iconDrawable, null, null, null)
- val onClickListener = View.OnClickListener {
- onSmartActionClick(entry, smartActions, actionIndex, action)
- }
+ val onClickListener =
+ View.OnClickListener {
+ onSmartActionClick(entry, smartActions, actionIndex, action)
+ }
setOnClickListener(
- if (delayOnClickListener)
- DelayedOnClickListener(onClickListener, constants.onClickInitDelay)
- else onClickListener)
+ if (delayOnClickListener)
+ DelayedOnClickListener(onClickListener, constants.onClickInitDelay)
+ else onClickListener
+ )
// Mark this as an Action button
(layoutParams as SmartReplyView.LayoutParams).mButtonType = SmartButtonType.ACTION
@@ -388,18 +430,31 @@ private fun loadIconDrawableWithTimeout(
entry: NotificationEntry,
smartActions: SmartActions,
actionIndex: Int,
- action: Notification.Action
+ action: Notification.Action,
) =
- if (smartActions.fromAssistant &&
- SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY == action.semanticAction) {
- entry.row.doSmartActionClick(entry.row.x.toInt() / 2,
- entry.row.y.toInt() / 2, SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY)
- smartReplyController
- .smartActionClicked(entry, actionIndex, action, smartActions.fromAssistant)
+ if (
+ smartActions.fromAssistant &&
+ SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY == action.semanticAction
+ ) {
+ entry.row.doSmartActionClick(
+ entry.row.x.toInt() / 2,
+ entry.row.y.toInt() / 2,
+ SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY,
+ )
+ smartReplyController.smartActionClicked(
+ entry,
+ actionIndex,
+ action,
+ smartActions.fromAssistant,
+ )
} else {
activityStarter.startPendingIntentDismissingKeyguard(action.actionIntent, entry.row) {
- smartReplyController
- .smartActionClicked(entry, actionIndex, action, smartActions.fromAssistant)
+ smartReplyController.smartActionClicked(
+ entry,
+ actionIndex,
+ action,
+ smartActions.fromAssistant,
+ )
}
}
}
@@ -411,16 +466,18 @@ interface SmartReplyInflater {
smartReplies: SmartReplies,
replyIndex: Int,
choice: CharSequence,
- delayOnClickListener: Boolean
+ delayOnClickListener: Boolean,
): Button
}
-class SmartReplyInflaterImpl @Inject constructor(
+class SmartReplyInflaterImpl
+@Inject
+constructor(
private val constants: SmartReplyConstants,
private val keyguardDismissUtil: KeyguardDismissUtil,
private val remoteInputManager: NotificationRemoteInputManager,
private val smartReplyController: SmartReplyController,
- private val context: Context
+ private val context: Context,
) : SmartReplyInflater {
override fun inflateReplyButton(
@@ -429,37 +486,35 @@ class SmartReplyInflaterImpl @Inject constructor(
smartReplies: SmartReplies,
replyIndex: Int,
choice: CharSequence,
- delayOnClickListener: Boolean
+ delayOnClickListener: Boolean,
): Button =
- (LayoutInflater.from(parent.context)
- .inflate(R.layout.smart_reply_button, parent, false) as Button
- ).apply {
+ (LayoutInflater.from(parent.context).inflate(R.layout.smart_reply_button, parent, false)
+ as Button)
+ .apply {
text = choice
- val onClickListener = View.OnClickListener {
- onSmartReplyClick(
- entry,
- smartReplies,
- replyIndex,
- parent,
- this,
- choice)
- }
+ val onClickListener =
+ View.OnClickListener {
+ onSmartReplyClick(entry, smartReplies, replyIndex, parent, this, choice)
+ }
setOnClickListener(
- if (delayOnClickListener)
- DelayedOnClickListener(onClickListener, constants.onClickInitDelay)
- else onClickListener)
- accessibilityDelegate = object : View.AccessibilityDelegate() {
- override fun onInitializeAccessibilityNodeInfo(
- host: View,
- info: AccessibilityNodeInfo
- ) {
- super.onInitializeAccessibilityNodeInfo(host, info)
- val label = parent.resources
- .getString(R.string.accessibility_send_smart_reply)
- val action = AccessibilityAction(AccessibilityNodeInfo.ACTION_CLICK, label)
- info.addAction(action)
+ if (delayOnClickListener)
+ DelayedOnClickListener(onClickListener, constants.onClickInitDelay)
+ else onClickListener
+ )
+ accessibilityDelegate =
+ object : View.AccessibilityDelegate() {
+ override fun onInitializeAccessibilityNodeInfo(
+ host: View,
+ info: AccessibilityNodeInfo,
+ ) {
+ super.onInitializeAccessibilityNodeInfo(host, info)
+ val label =
+ parent.resources.getString(R.string.accessibility_send_smart_reply)
+ val action =
+ AccessibilityAction(AccessibilityNodeInfo.ACTION_CLICK, label)
+ info.addAction(action)
+ }
}
- }
// TODO: probably shouldn't do this here, bad API
// Mark this as a Reply button
(layoutParams as SmartReplyView.LayoutParams).mButtonType = SmartButtonType.REPLY
@@ -471,39 +526,52 @@ class SmartReplyInflaterImpl @Inject constructor(
replyIndex: Int,
smartReplyView: SmartReplyView,
button: Button,
- choice: CharSequence
- ) = keyguardDismissUtil.executeWhenUnlocked(!entry.isRowPinned) {
- val canEditBeforeSend = constants.getEffectiveEditChoicesBeforeSending(
- smartReplies.remoteInput.editChoicesBeforeSending)
- if (canEditBeforeSend) {
- remoteInputManager.activateRemoteInput(
+ choice: CharSequence,
+ ) =
+ keyguardDismissUtil.executeWhenUnlocked(!entry.isRowPinned) {
+ val canEditBeforeSend =
+ constants.getEffectiveEditChoicesBeforeSending(
+ smartReplies.remoteInput.editChoicesBeforeSending
+ )
+ if (canEditBeforeSend) {
+ remoteInputManager.activateRemoteInput(
button,
arrayOf(smartReplies.remoteInput),
smartReplies.remoteInput,
smartReplies.pendingIntent,
- NotificationEntry.EditedSuggestionInfo(choice, replyIndex))
- } else {
- smartReplyController.smartReplySent(
+ NotificationEntry.EditedSuggestionInfo(choice, replyIndex),
+ )
+ } else {
+ smartReplyController.smartReplySent(
entry,
replyIndex,
button.text,
NotificationLogger.getNotificationLocation(entry).toMetricsEventEnum(),
- false /* modifiedBeforeSending */)
- entry.setHasSentReply()
- try {
- val intent = createRemoteInputIntent(smartReplies, choice)
- val opts = ActivityOptions.makeBasic()
- opts.setPendingIntentBackgroundActivityStartMode(
- ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
- smartReplies.pendingIntent.send(context, 0, intent, /* onFinished */null,
- /* handler */ null, /* requiredPermission */ null, opts.toBundle())
- } catch (e: PendingIntent.CanceledException) {
- Log.w(TAG, "Unable to send smart reply", e)
+ false, /* modifiedBeforeSending */
+ )
+ entry.setHasSentReply()
+ try {
+ val intent = createRemoteInputIntent(smartReplies, choice)
+ val opts = ActivityOptions.makeBasic()
+ opts.setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+ )
+ smartReplies.pendingIntent.send(
+ context,
+ 0,
+ intent, /* onFinished */
+ null,
+ /* handler */ null, /* requiredPermission */
+ null,
+ opts.toBundle(),
+ )
+ } catch (e: PendingIntent.CanceledException) {
+ Log.w(TAG, "Unable to send smart reply", e)
+ }
+ smartReplyView.hideSmartSuggestions()
}
- smartReplyView.hideSmartSuggestions()
+ false // do not defer
}
- false // do not defer
- }
private fun createRemoteInputIntent(smartReplies: SmartReplies, choice: CharSequence): Intent {
val results = Bundle()
@@ -516,12 +584,11 @@ class SmartReplyInflaterImpl @Inject constructor(
}
/**
- * An OnClickListener wrapper that blocks the underlying OnClickListener for a given amount of
- * time.
+ * An OnClickListener wrapper that blocks the underlying OnClickListener for a given amount of time.
*/
private class DelayedOnClickListener(
private val mActualListener: View.OnClickListener,
- private val mInitDelayMs: Long
+ private val mInitDelayMs: Long,
) : View.OnClickListener {
private val mInitTimeMs = SystemClock.elapsedRealtime()
@@ -535,7 +602,7 @@ private class DelayedOnClickListener(
}
private fun hasFinishedInitialization(): Boolean =
- SystemClock.elapsedRealtime() >= mInitTimeMs + mInitDelayMs
+ SystemClock.elapsedRealtime() >= mInitTimeMs + mInitDelayMs
}
private const val TAG = "SmartReplyViewInflater"
@@ -544,12 +611,12 @@ private val DEBUG = Log.isLoggable(TAG, Log.DEBUG)
// convenience function that swaps parameter order so that lambda can be placed at the end
private fun KeyguardDismissUtil.executeWhenUnlocked(
requiresShadeOpen: Boolean,
- onDismissAction: () -> Boolean
+ onDismissAction: () -> Boolean,
) = executeWhenUnlocked(onDismissAction, requiresShadeOpen, false)
// convenience function that swaps parameter order so that lambda can be placed at the end
private fun ActivityStarter.startPendingIntentDismissingKeyguard(
intent: PendingIntent,
associatedView: View?,
- runnable: () -> Unit
-) = startPendingIntentDismissingKeyguard(intent, runnable::invoke, associatedView) \ No newline at end of file
+ runnable: () -> Unit,
+) = startPendingIntentDismissingKeyguard(intent, runnable::invoke, associatedView)
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
index bf13ceb5666a..4bc5f2303ee8 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
@@ -45,6 +45,8 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.provider.Settings;
import android.telephony.ServiceState;
import android.telephony.SubscriptionInfo;
@@ -56,6 +58,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.keyguard.logging.CarrierTextManagerLogger;
+import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.kosmos.KosmosJavaAdapter;
@@ -200,6 +203,7 @@ public class CarrierTextManagerTest extends SysuiTestCase {
}
@Test
+ @DisableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
public void testAirplaneMode() {
Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);
reset(mCarrierTextCallback);
@@ -220,8 +224,32 @@ public class CarrierTextManagerTest extends SysuiTestCase {
assertEquals(AIRPLANE_MODE_TEXT, captor.getValue().carrierText);
}
+ @Test
+ @EnableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
+ public void testAirplaneMode_flagEnabled() {
+ Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);
+ reset(mCarrierTextCallback);
+ List<SubscriptionInfo> list = new ArrayList<>();
+ list.add(TEST_SUBSCRIPTION);
+ when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(list);
+ when(mKeyguardUpdateMonitor.getSimStateForSlotId(0)).thenReturn(
+ TelephonyManager.SIM_STATE_READY);
+ mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
+
+ mCarrierTextManager.updateCarrierText();
+
+ ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
+ ArgumentCaptor.forClass(
+ CarrierTextManager.CarrierTextCallbackInfo.class);
+
+ FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
+ verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
+ assertEquals(AIRPLANE_MODE_TEXT, captor.getValue().carrierText);
+ }
+
/** regression test for b/281706473, caused by sending NULL plmn / spn to the logger */
@Test
+ @DisableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
public void testAirplaneMode_noSim_nullPlmn_nullSpn_doesNotCrash() {
// GIVEN - sticy broadcast that returns a null PLMN and null SPN
Intent stickyIntent = new Intent(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED);
@@ -263,7 +291,52 @@ public class CarrierTextManagerTest extends SysuiTestCase {
// No assert, this test should not crash
}
+ /** regression test for b/281706473, caused by sending NULL plmn / spn to the logger */
+ @Test
+ @EnableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
+ public void testAirplaneMode_noSim_nullPlmn_nullSpn_doesNotCrash_flagEnabled() {
+ // GIVEN - sticy broadcast that returns a null PLMN and null SPN
+ Intent stickyIntent = new Intent(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED);
+ stickyIntent.putExtra(TelephonyManager.EXTRA_SHOW_PLMN, true);
+ stickyIntent.removeExtra(TelephonyManager.EXTRA_PLMN);
+ stickyIntent.putExtra(TelephonyManager.EXTRA_SHOW_SPN, true);
+ stickyIntent.removeExtra(TelephonyManager.EXTRA_SPN);
+
+ mCarrierTextManager = new CarrierTextManager.Builder(
+ getContextSpyForStickyBroadcast(stickyIntent),
+ mContext.getResources(),
+ mWifiRepository,
+ mSatelliteViewModel,
+ mJavaAdapter,
+ mTelephonyManager,
+ mTelephonyListenerManager,
+ mWakefulnessLifecycle,
+ mMainExecutor,
+ mBgExecutor,
+ mKeyguardUpdateMonitor,
+ mLogger
+ )
+ .setShowAirplaneMode(true)
+ .setShowMissingSim(true)
+ .build();
+
+ // GIVEN - airplane mode is off (causing CTM to fetch the sticky broadcast)
+ Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);
+ reset(mCarrierTextCallback);
+ List<SubscriptionInfo> list = new ArrayList<>();
+ when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(list);
+ when(mKeyguardUpdateMonitor.getSimStateForSlotId(0))
+ .thenReturn(TelephonyManager.SIM_STATE_NOT_READY);
+ mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
+
+ // WHEN CTM fetches the broadcast and attempts to log the result, no crash results
+ mCarrierTextManager.updateCarrierText();
+
+ // No assert, this test should not crash
+ }
+
@Test
+ @DisableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
public void testCardIOError() {
reset(mCarrierTextCallback);
List<SubscriptionInfo> list = new ArrayList<>();
@@ -299,6 +372,44 @@ public class CarrierTextManagerTest extends SysuiTestCase {
}
@Test
+ @EnableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
+ public void testCardIOError_flagEnabled() {
+ reset(mCarrierTextCallback);
+ List<SubscriptionInfo> list = new ArrayList<>();
+ list.add(TEST_SUBSCRIPTION);
+ when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(list);
+ when(mKeyguardUpdateMonitor.getSimStateForSlotId(0)).thenReturn(
+ TelephonyManager.SIM_STATE_READY);
+ when(mKeyguardUpdateMonitor.getSimStateForSlotId(1)).thenReturn(
+ TelephonyManager.SIM_STATE_CARD_IO_ERROR);
+ mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
+
+ mCarrierTextManager.mCallback.onSimStateChanged(3, 1,
+ TelephonyManager.SIM_STATE_CARD_IO_ERROR);
+
+ ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
+ ArgumentCaptor.forClass(
+ CarrierTextManager.CarrierTextCallbackInfo.class);
+
+ FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
+ verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
+ assertEquals("TEST_CARRIER" + SEPARATOR + INVALID_CARD_TEXT, captor.getValue().carrierText);
+ // There's only one subscription in the list
+ assertEquals(1, captor.getValue().listOfCarriers.length);
+ assertEquals(TEST_CARRIER, captor.getValue().listOfCarriers[0]);
+
+ // Now it becomes single SIM active mode.
+ reset(mCarrierTextCallback);
+ when(mTelephonyManager.getActiveModemCount()).thenReturn(1);
+ // Update carrier text. It should ignore error state of subId 3 in inactive slotId.
+ mCarrierTextManager.updateCarrierText();
+ FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
+ verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
+ assertEquals("TEST_CARRIER", captor.getValue().carrierText);
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
public void testWrongSlots() {
reset(mCarrierTextCallback);
when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(
@@ -313,6 +424,22 @@ public class CarrierTextManagerTest extends SysuiTestCase {
}
@Test
+ @EnableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
+ public void testWrongSlots_flagEnabled() {
+ reset(mCarrierTextCallback);
+ when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(
+ new ArrayList<>());
+ when(mKeyguardUpdateMonitor.getSimStateForSlotId(anyInt())).thenReturn(
+ TelephonyManager.SIM_STATE_CARD_IO_ERROR);
+ // This should not produce an out of bounds error, even though there are no subscriptions
+ mCarrierTextManager.mCallback.onSimStateChanged(0, -3,
+ TelephonyManager.SIM_STATE_CARD_IO_ERROR);
+ mCarrierTextManager.mCallback.onSimStateChanged(0, 3, TelephonyManager.SIM_STATE_READY);
+ verify(mCarrierTextCallback, never()).updateCarrierInfo(any());
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
public void testMoreSlotsThanSubs() {
reset(mCarrierTextCallback);
when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(
@@ -335,6 +462,29 @@ public class CarrierTextManagerTest extends SysuiTestCase {
}
@Test
+ @EnableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
+ public void testMoreSlotsThanSubs_flagEnabled() {
+ reset(mCarrierTextCallback);
+ when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(
+ new ArrayList<>());
+
+ // STOPSHIP(b/130246708) This line makes sure that SubscriptionManager provides the
+ // same answer as KeyguardUpdateMonitor. Remove when this is addressed
+ when(mSubscriptionManager.getCompleteActiveSubscriptionInfoList()).thenReturn(
+ new ArrayList<>());
+
+ when(mKeyguardUpdateMonitor.getSimStateForSlotId(anyInt())).thenReturn(
+ TelephonyManager.SIM_STATE_CARD_IO_ERROR);
+ // This should not produce an out of bounds error, even though there are no subscriptions
+ mCarrierTextManager.mCallback.onSimStateChanged(0, 1,
+ TelephonyManager.SIM_STATE_CARD_IO_ERROR);
+
+ FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
+ verify(mCarrierTextCallback).updateCarrierInfo(
+ any(CarrierTextManager.CarrierTextCallbackInfo.class));
+ }
+
+ @Test
public void testCallback() {
reset(mCarrierTextCallback);
mCarrierTextManager.postToCallback(mCarrierTextCallbackInfo);
@@ -360,6 +510,7 @@ public class CarrierTextManagerTest extends SysuiTestCase {
}
@Test
+ @DisableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
public void testCreateInfo_OneValidSubscription() {
reset(mCarrierTextCallback);
List<SubscriptionInfo> list = new ArrayList<>();
@@ -385,6 +536,33 @@ public class CarrierTextManagerTest extends SysuiTestCase {
}
@Test
+ @EnableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
+ public void testCreateInfo_OneValidSubscription_flagEnabled() {
+ reset(mCarrierTextCallback);
+ List<SubscriptionInfo> list = new ArrayList<>();
+ list.add(TEST_SUBSCRIPTION);
+ when(mKeyguardUpdateMonitor.getSimStateForSlotId(anyInt())).thenReturn(
+ TelephonyManager.SIM_STATE_READY);
+ when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(list);
+
+ mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
+
+ ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
+ ArgumentCaptor.forClass(
+ CarrierTextManager.CarrierTextCallbackInfo.class);
+
+ mCarrierTextManager.updateCarrierText();
+ FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
+ verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
+
+ CarrierTextManager.CarrierTextCallbackInfo info = captor.getValue();
+ assertEquals(1, info.listOfCarriers.length);
+ assertEquals(TEST_CARRIER, info.listOfCarriers[0]);
+ assertEquals(1, info.subscriptionIds.length);
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
public void testCreateInfo_OneValidSubscriptionWithRoaming() {
reset(mCarrierTextCallback);
List<SubscriptionInfo> list = new ArrayList<>();
@@ -410,6 +588,33 @@ public class CarrierTextManagerTest extends SysuiTestCase {
}
@Test
+ @EnableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
+ public void testCreateInfo_OneValidSubscriptionWithRoaming_flagEnabled() {
+ reset(mCarrierTextCallback);
+ List<SubscriptionInfo> list = new ArrayList<>();
+ list.add(TEST_SUBSCRIPTION_ROAMING);
+ when(mKeyguardUpdateMonitor.getSimStateForSlotId(anyInt())).thenReturn(
+ TelephonyManager.SIM_STATE_READY);
+ when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(list);
+
+ mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
+
+ ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
+ ArgumentCaptor.forClass(
+ CarrierTextManager.CarrierTextCallbackInfo.class);
+
+ mCarrierTextManager.updateCarrierText();
+ FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
+ verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
+
+ CarrierTextManager.CarrierTextCallbackInfo info = captor.getValue();
+ assertEquals(1, info.listOfCarriers.length);
+ assertTrue(info.listOfCarriers[0].toString().contains(TEST_CARRIER));
+ assertEquals(1, info.subscriptionIds.length);
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
public void testCarrierText_noTextOnReadySimWhenNull() {
reset(mCarrierTextCallback);
List<SubscriptionInfo> list = new ArrayList<>();
@@ -434,6 +639,32 @@ public class CarrierTextManagerTest extends SysuiTestCase {
}
@Test
+ @EnableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
+ public void testCarrierText_noTextOnReadySimWhenNull_flagEnabled() {
+ reset(mCarrierTextCallback);
+ List<SubscriptionInfo> list = new ArrayList<>();
+ list.add(TEST_SUBSCRIPTION_NULL);
+ when(mKeyguardUpdateMonitor.getSimStateForSlotId(anyInt())).thenReturn(
+ TelephonyManager.SIM_STATE_READY);
+ when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(list);
+
+ mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
+
+ ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
+ ArgumentCaptor.forClass(
+ CarrierTextManager.CarrierTextCallbackInfo.class);
+
+ mCarrierTextManager.updateCarrierText();
+ FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
+ verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
+
+ assertTrue("Carrier text should be empty, instead it's " + captor.getValue().carrierText,
+ TextUtils.isEmpty(captor.getValue().carrierText));
+ assertFalse("No SIM should be available", captor.getValue().anySimReady);
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
public void testCarrierText_noTextOnReadySimWhenNull_airplaneMode_wifiOn() {
Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);
reset(mCarrierTextCallback);
@@ -472,6 +703,46 @@ public class CarrierTextManagerTest extends SysuiTestCase {
}
@Test
+ @EnableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
+ public void testCarrierText_noTextOnReadySimWhenNull_airplaneMode_wifiOn_flagEnabled() {
+ Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 1);
+ reset(mCarrierTextCallback);
+ List<SubscriptionInfo> list = new ArrayList<>();
+ list.add(TEST_SUBSCRIPTION_NULL);
+ when(mKeyguardUpdateMonitor.getSimStateForSlotId(anyInt())).thenReturn(
+ TelephonyManager.SIM_STATE_READY);
+ when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(list);
+
+ assertFalse(mWifiRepository.isWifiConnectedWithValidSsid());
+ mWifiRepository.setWifiNetwork(
+ WifiNetworkModel.Active.Companion.of(
+ /* isValidated= */ false,
+ /* level= */ 0,
+ /* ssid= */ "",
+ /* hotspotDeviceType= */ WifiNetworkModel.HotspotDeviceType.NONE));
+ assertTrue(mWifiRepository.isWifiConnectedWithValidSsid());
+
+ mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
+ ServiceState ss = mock(ServiceState.class);
+ when(ss.getDataRegistrationState()).thenReturn(ServiceState.STATE_IN_SERVICE);
+ mKeyguardUpdateMonitor.mServiceStates.put(TEST_SUBSCRIPTION_NULL.getSubscriptionId(), ss);
+
+ ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
+ ArgumentCaptor.forClass(
+ CarrierTextManager.CarrierTextCallbackInfo.class);
+
+ mCarrierTextManager.updateCarrierText();
+ FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
+ verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
+
+ assertFalse("No SIM should be available", captor.getValue().anySimReady);
+ // There's no airplane mode if at least one SIM is State.READY and there's wifi
+ assertFalse("Device should not be in airplane mode", captor.getValue().airplaneMode);
+ assertNotEquals(AIRPLANE_MODE_TEXT, captor.getValue().carrierText);
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
public void carrierText_satelliteTextNull_isSatelliteFalse_textNotUsed() {
reset(mCarrierTextCallback);
List<SubscriptionInfo> list = new ArrayList<>();
@@ -497,6 +768,33 @@ public class CarrierTextManagerTest extends SysuiTestCase {
}
@Test
+ @EnableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
+ public void carrierText_satelliteTextNull_isSatelliteFalse_textNotUsed_flagEnabled() {
+ reset(mCarrierTextCallback);
+ List<SubscriptionInfo> list = new ArrayList<>();
+ list.add(TEST_SUBSCRIPTION);
+ when(mKeyguardUpdateMonitor.getSimStateForSlotId(anyInt())).thenReturn(
+ TelephonyManager.SIM_STATE_READY);
+ when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(list);
+ mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
+
+ // WHEN the satellite text is null
+ mSatelliteViewModel.getCarrierText().setValue(null);
+ mTestScope.getTestScheduler().runCurrent();
+
+ ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
+ ArgumentCaptor.forClass(
+ CarrierTextManager.CarrierTextCallbackInfo.class);
+ FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
+
+ // THEN satellite mode is false and the default subscription carrier text is used
+ verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
+ assertThat(captor.getValue().isInSatelliteMode).isFalse();
+ assertThat(captor.getValue().carrierText).isEqualTo(TEST_CARRIER);
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
public void carrierText_hasSatelliteText_isSatelliteTrue_textUsed() {
reset(mCarrierTextCallback);
List<SubscriptionInfo> list = new ArrayList<>();
@@ -522,6 +820,33 @@ public class CarrierTextManagerTest extends SysuiTestCase {
}
@Test
+ @EnableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
+ public void carrierText_hasSatelliteText_isSatelliteTrue_textUsed_flagEnabled() {
+ reset(mCarrierTextCallback);
+ List<SubscriptionInfo> list = new ArrayList<>();
+ list.add(TEST_SUBSCRIPTION);
+ when(mKeyguardUpdateMonitor.getSimStateForSlotId(anyInt())).thenReturn(
+ TelephonyManager.SIM_STATE_READY);
+ when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(list);
+ mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
+
+ // WHEN the satellite text is non-null
+ mSatelliteViewModel.getCarrierText().setValue("Satellite Test Text");
+ mTestScope.getTestScheduler().runCurrent();
+
+ ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
+ ArgumentCaptor.forClass(
+ CarrierTextManager.CarrierTextCallbackInfo.class);
+ FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
+
+ // THEN satellite mode is true and the satellite text is used
+ verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
+ assertThat(captor.getValue().isInSatelliteMode).isTrue();
+ assertThat(captor.getValue().carrierText).isEqualTo("Satellite Test Text");
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
public void carrierText_satelliteTextUpdates_autoTriggersCallback() {
reset(mCarrierTextCallback);
List<SubscriptionInfo> list = new ArrayList<>();
@@ -559,6 +884,45 @@ public class CarrierTextManagerTest extends SysuiTestCase {
}
@Test
+ @EnableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
+ public void carrierText_satelliteTextUpdates_autoTriggersCallback_flagEnabled() {
+ reset(mCarrierTextCallback);
+ List<SubscriptionInfo> list = new ArrayList<>();
+ list.add(TEST_SUBSCRIPTION);
+ when(mKeyguardUpdateMonitor.getSimStateForSlotId(anyInt())).thenReturn(
+ TelephonyManager.SIM_STATE_READY);
+ when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(list);
+ mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
+
+ // WHEN the satellite text is set
+ mSatelliteViewModel.getCarrierText().setValue("Test satellite text");
+ mTestScope.getTestScheduler().runCurrent();
+
+ // THEN we should automatically re-trigger #updateCarrierText and get callback info
+ ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
+ ArgumentCaptor.forClass(
+ CarrierTextManager.CarrierTextCallbackInfo.class);
+ FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
+ verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
+ // AND use the satellite text as the carrier text
+ assertThat(captor.getValue().isInSatelliteMode).isTrue();
+ assertThat(captor.getValue().carrierText).isEqualTo("Test satellite text");
+
+ // WHEN the satellite text is reset to null
+ reset(mCarrierTextCallback);
+ mSatelliteViewModel.getCarrierText().setValue(null);
+ mTestScope.getTestScheduler().runCurrent();
+
+ // THEN we should automatically re-trigger #updateCarrierText and get callback info
+ // that doesn't include the satellite info
+ FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
+ verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
+ assertThat(captor.getValue().isInSatelliteMode).isFalse();
+ assertThat(captor.getValue().carrierText).isEqualTo(TEST_CARRIER);
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
public void carrierText_updatedWhileNotListening_getsNewValueWhenListening() {
reset(mCarrierTextCallback);
List<SubscriptionInfo> list = new ArrayList<>();
@@ -602,6 +966,50 @@ public class CarrierTextManagerTest extends SysuiTestCase {
}
@Test
+ @EnableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
+ public void carrierText_updatedWhileNotListening_getsNewValueWhenListening_flagEnabled() {
+ reset(mCarrierTextCallback);
+ List<SubscriptionInfo> list = new ArrayList<>();
+ list.add(TEST_SUBSCRIPTION);
+ when(mKeyguardUpdateMonitor.getSimStateForSlotId(anyInt())).thenReturn(
+ TelephonyManager.SIM_STATE_READY);
+ when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(list);
+ mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
+
+ mSatelliteViewModel.getCarrierText().setValue("Old satellite text");
+ mTestScope.getTestScheduler().runCurrent();
+
+ ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
+ ArgumentCaptor.forClass(
+ CarrierTextManager.CarrierTextCallbackInfo.class);
+ FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
+ verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
+ assertThat(captor.getValue().carrierText).isEqualTo("Old satellite text");
+
+ // WHEN we stop listening
+ reset(mCarrierTextCallback);
+ mCarrierTextManager.setListening(null);
+
+ // AND the satellite text updates
+ mSatelliteViewModel.getCarrierText().setValue("New satellite text");
+
+ // THEN we don't get new callback info because we aren't listening
+ verify(mCarrierTextCallback, never()).updateCarrierInfo(any());
+
+ // WHEN we start listening again
+ reset(mCarrierTextCallback);
+ mCarrierTextManager.setListening(mCarrierTextCallback);
+
+ // THEN we should automatically re-trigger #updateCarrierText and get callback info
+ // that includes the new satellite state and text
+ mTestScope.getTestScheduler().runCurrent();
+ FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
+ verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
+ assertThat(captor.getValue().isInSatelliteMode).isTrue();
+ assertThat(captor.getValue().carrierText).isEqualTo("New satellite text");
+ }
+
+ @Test
public void testCreateInfo_noSubscriptions() {
reset(mCarrierTextCallback);
when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(
@@ -622,6 +1030,7 @@ public class CarrierTextManagerTest extends SysuiTestCase {
}
@Test
+ @DisableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
public void testCarrierText_oneValidSubscription() {
reset(mCarrierTextCallback);
List<SubscriptionInfo> list = new ArrayList<>();
@@ -644,6 +1053,30 @@ public class CarrierTextManagerTest extends SysuiTestCase {
}
@Test
+ @EnableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
+ public void testCarrierText_oneValidSubscription_flagEnabled() {
+ reset(mCarrierTextCallback);
+ List<SubscriptionInfo> list = new ArrayList<>();
+ list.add(TEST_SUBSCRIPTION);
+ when(mKeyguardUpdateMonitor.getSimStateForSlotId(anyInt())).thenReturn(
+ TelephonyManager.SIM_STATE_READY);
+ when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(list);
+
+ mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
+
+ ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
+ ArgumentCaptor.forClass(
+ CarrierTextManager.CarrierTextCallbackInfo.class);
+
+ mCarrierTextManager.updateCarrierText();
+ FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
+ verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
+
+ assertThat(captor.getValue().carrierText).isEqualTo(TEST_CARRIER);
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
public void testCarrierText_twoValidSubscriptions() {
reset(mCarrierTextCallback);
List<SubscriptionInfo> list = new ArrayList<>();
@@ -668,6 +1101,32 @@ public class CarrierTextManagerTest extends SysuiTestCase {
}
@Test
+ @EnableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
+ public void testCarrierText_twoValidSubscriptions_flagEnabled() {
+ reset(mCarrierTextCallback);
+ List<SubscriptionInfo> list = new ArrayList<>();
+ list.add(TEST_SUBSCRIPTION);
+ list.add(TEST_SUBSCRIPTION);
+ when(mKeyguardUpdateMonitor.getSimStateForSlotId(anyInt())).thenReturn(
+ TelephonyManager.SIM_STATE_READY);
+ when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(list);
+
+ mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
+
+ ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
+ ArgumentCaptor.forClass(
+ CarrierTextManager.CarrierTextCallbackInfo.class);
+
+ mCarrierTextManager.updateCarrierText();
+ FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
+ verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
+
+ assertEquals(TEST_CARRIER + SEPARATOR + TEST_CARRIER,
+ captor.getValue().carrierText);
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
public void testCarrierText_oneDisabledSub() {
reset(mCarrierTextCallback);
List<SubscriptionInfo> list = new ArrayList<>();
@@ -693,6 +1152,33 @@ public class CarrierTextManagerTest extends SysuiTestCase {
}
@Test
+ @EnableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
+ public void testCarrierText_oneDisabledSub_flagEnabled() {
+ reset(mCarrierTextCallback);
+ List<SubscriptionInfo> list = new ArrayList<>();
+ list.add(TEST_SUBSCRIPTION);
+ list.add(TEST_SUBSCRIPTION);
+ when(mKeyguardUpdateMonitor.getSimStateForSlotId(anyInt()))
+ .thenReturn(TelephonyManager.SIM_STATE_READY)
+ .thenReturn(TelephonyManager.SIM_STATE_NOT_READY);
+ when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(list);
+
+ mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
+
+ ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
+ ArgumentCaptor.forClass(
+ CarrierTextManager.CarrierTextCallbackInfo.class);
+
+ mCarrierTextManager.updateCarrierText();
+ FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
+ verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
+
+ assertEquals(TEST_CARRIER,
+ captor.getValue().carrierText);
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
public void testCarrierText_firstDisabledSub() {
reset(mCarrierTextCallback);
List<SubscriptionInfo> list = new ArrayList<>();
@@ -718,6 +1204,33 @@ public class CarrierTextManagerTest extends SysuiTestCase {
}
@Test
+ @EnableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
+ public void testCarrierText_firstDisabledSub_flagEnabled() {
+ reset(mCarrierTextCallback);
+ List<SubscriptionInfo> list = new ArrayList<>();
+ list.add(TEST_SUBSCRIPTION);
+ list.add(TEST_SUBSCRIPTION);
+ when(mKeyguardUpdateMonitor.getSimStateForSlotId(anyInt()))
+ .thenReturn(TelephonyManager.SIM_STATE_NOT_READY)
+ .thenReturn(TelephonyManager.SIM_STATE_READY);
+ when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(list);
+
+ mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
+
+ ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
+ ArgumentCaptor.forClass(
+ CarrierTextManager.CarrierTextCallbackInfo.class);
+
+ mCarrierTextManager.updateCarrierText();
+ FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
+ verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
+
+ assertEquals(TEST_CARRIER,
+ captor.getValue().carrierText);
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
public void testCarrierText_threeSubsMiddleDisabled() {
reset(mCarrierTextCallback);
List<SubscriptionInfo> list = new ArrayList<>();
@@ -744,6 +1257,33 @@ public class CarrierTextManagerTest extends SysuiTestCase {
}
@Test
+ @EnableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
+ public void testCarrierText_threeSubsMiddleDisabled_flagEnabled() {
+ reset(mCarrierTextCallback);
+ List<SubscriptionInfo> list = new ArrayList<>();
+ list.add(TEST_SUBSCRIPTION);
+ list.add(TEST_SUBSCRIPTION);
+ list.add(TEST_SUBSCRIPTION);
+ when(mKeyguardUpdateMonitor.getSimStateForSlotId(anyInt()))
+ .thenReturn(TelephonyManager.SIM_STATE_READY)
+ .thenReturn(TelephonyManager.SIM_STATE_NOT_READY)
+ .thenReturn(TelephonyManager.SIM_STATE_READY);
+ when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(list);
+ mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
+
+ ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
+ ArgumentCaptor.forClass(
+ CarrierTextManager.CarrierTextCallbackInfo.class);
+
+ mCarrierTextManager.updateCarrierText();
+ FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
+ verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
+
+ assertEquals(TEST_CARRIER + SEPARATOR + TEST_CARRIER,
+ captor.getValue().carrierText);
+ }
+
+ @Test
public void testGetStatusForIccState() {
when(mKeyguardUpdateMonitor.isDeviceProvisioned()).thenReturn(false);
assertEquals(CarrierTextManager.StatusMode.SimMissingLocked,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index a39ca5de787d..839a2bdf5588 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -103,6 +103,7 @@ import android.os.PowerManager;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
+import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.FlagsParameterization;
import android.service.dreams.IDreamManager;
@@ -200,8 +201,9 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
private static final String TEST_CARRIER_2 = "TEST_CARRIER_2";
private static final int TEST_CARRIER_ID = 1;
private static final String TEST_GROUP_UUID = "59b5c870-fc4c-47a4-a99e-9db826b48b24";
- private static final SubscriptionInfo TEST_SUBSCRIPTION = new SubscriptionInfo(1, "", 0,
- TEST_CARRIER, TEST_CARRIER, NAME_SOURCE_CARRIER_ID, 0xFFFFFF, "",
+ private static final int TEST_SLOT_ID = 3;
+ private static final SubscriptionInfo TEST_SUBSCRIPTION = new SubscriptionInfo(1, "",
+ TEST_SLOT_ID, TEST_CARRIER, TEST_CARRIER, NAME_SOURCE_CARRIER_ID, 0xFFFFFF, "",
DATA_ROAMING_DISABLE, null, null, null, null, false, null, "", false, TEST_GROUP_UUID,
TEST_CARRIER_ID, 0);
private static final SubscriptionInfo TEST_SUBSCRIPTION_2 = new SubscriptionInfo(2, "", 0,
@@ -483,7 +485,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
}
@Test
- public void testSimStateInitialized() {
+ @DisableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
+ public void testSimStateInitialized_flagDisabled() {
cleanupKeyguardUpdateMonitor();
final int subId = 3;
final int state = TelephonyManager.SIM_STATE_ABSENT;
@@ -500,6 +503,24 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
}
@Test
+ @EnableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
+ public void testSimStateInitialized_flagEnabled() {
+ cleanupKeyguardUpdateMonitor();
+ final int state = TelephonyManager.SIM_STATE_ABSENT;
+ final int slotId = 0;
+ final int subId = 3;
+ when(mTelephonyManager.getActiveModemCount()).thenReturn(1);
+ when(mTelephonyManager.getSimState(anyInt())).thenReturn(state);
+ when(mSubscriptionManager.getSubscriptionIds(anyInt())).thenReturn(new int[]{subId});
+
+ KeyguardUpdateMonitor testKUM = new TestableKeyguardUpdateMonitor(mContext);
+
+ mTestableLooper.processAllMessages();
+
+ assertThat(testKUM.getSimStateForSlotId(slotId)).isEqualTo(state);
+ }
+
+ @Test
public void testIgnoresSimStateCallback_rebroadcast() {
Intent intent = defaultSimStateChangedIntent();
@@ -1240,7 +1261,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
}
@Test
- public void testActiveSubscriptionBecomesInactive() {
+ @DisableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
+ public void testActiveSubscriptionBecomesInactive_flagDisabled() {
List<SubscriptionInfo> list = new ArrayList<>();
list.add(TEST_SUBSCRIPTION);
when(mSubscriptionManager.getCompleteActiveSubscriptionInfoList()).thenReturn(list);
@@ -1263,6 +1285,28 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
}
@Test
+ @EnableFlags(Flags.FLAG_SIM_PIN_USE_SLOT_ID)
+ public void testActiveSubscriptionBecomesInactive_flagEnabled() {
+ List<SubscriptionInfo> list = new ArrayList<>();
+ list.add(TEST_SUBSCRIPTION);
+ when(mSubscriptionManager.getCompleteActiveSubscriptionInfoList()).thenReturn(list);
+ mKeyguardUpdateMonitor.mPhoneStateListener.onActiveDataSubscriptionIdChanged(
+ TEST_SUBSCRIPTION.getSubscriptionId());
+ mTestableLooper.processAllMessages();
+ assertThat(mKeyguardUpdateMonitor.mSimDatasBySlotId.get(TEST_SLOT_ID))
+ .isNotNull();
+
+ when(mSubscriptionManager.getCompleteActiveSubscriptionInfoList())
+ .thenReturn(new ArrayList<>());
+ mKeyguardUpdateMonitor.mPhoneStateListener.onActiveDataSubscriptionIdChanged(
+ SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+ mTestableLooper.processAllMessages();
+
+ assertThat(mKeyguardUpdateMonitor.mSimDatasBySlotId.get(TEST_SLOT_ID))
+ .isNull();
+ }
+
+ @Test
public void testActiveSubscriptionList_filtersProvisioningNetworks() {
List<SubscriptionInfo> list = new ArrayList<>();
list.add(TEST_SUBSCRIPTION_PROVISIONING);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
index f41d5c8eeb23..8552e48a2024 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
@@ -63,8 +63,6 @@ import android.graphics.Rect;
import android.os.Handler;
import android.os.RemoteException;
import android.os.SystemClock;
-import android.platform.test.annotations.DisableFlags;
-import android.platform.test.annotations.EnableFlags;
import android.provider.Settings;
import android.testing.TestableLooper;
import android.testing.TestableResources;
@@ -90,7 +88,6 @@ import androidx.test.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
-import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.AnimatorTestRule;
import com.android.systemui.kosmos.KosmosJavaAdapter;
@@ -612,46 +609,6 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
.isEqualTo(expectedRatio);
}
- @DisableFlags(Flags.FLAG_SAVE_AND_RESTORE_MAGNIFICATION_SETTINGS_BUTTONS)
- @Test
- public void onScreenSizeAndDensityChanged_enabled_restoreSavedMagnifierWindow() {
- int newSmallestScreenWidthDp =
- mContext.getResources().getConfiguration().smallestScreenWidthDp * 2;
- int windowFrameSize = mResources.getDimensionPixelSize(
- com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
- Size preferredWindowSize = new Size(windowFrameSize, windowFrameSize);
- mSharedPreferences
- .edit()
- .putString(String.valueOf(newSmallestScreenWidthDp),
- preferredWindowSize.toString())
- .commit();
- mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationController.updateWindowMagnificationInternal(Float.NaN, Float.NaN,
- Float.NaN);
- });
-
- // Screen density and size change
- mContext.getResources().getConfiguration().smallestScreenWidthDp = newSmallestScreenWidthDp;
- final Rect testWindowBounds = new Rect(
- mWindowManager.getCurrentWindowMetrics().getBounds());
- testWindowBounds.set(testWindowBounds.left, testWindowBounds.top,
- testWindowBounds.right + 100, testWindowBounds.bottom + 100);
- mWindowManager.setWindowBounds(testWindowBounds);
- mInstrumentation.runOnMainSync(() -> {
- mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_SCREEN_SIZE);
- });
-
- // wait for rect update
- waitForIdleSync();
- ViewGroup.LayoutParams params = mSurfaceControlViewHost.getView().getLayoutParams();
- final int mirrorSurfaceMargin = mResources.getDimensionPixelSize(
- R.dimen.magnification_mirror_surface_margin);
- // The width and height of the view include the magnification frame and the margins.
- assertThat(params.width).isEqualTo(windowFrameSize + 2 * mirrorSurfaceMargin);
- assertThat(params.height).isEqualTo(windowFrameSize + 2 * mirrorSurfaceMargin);
- }
-
- @EnableFlags(Flags.FLAG_SAVE_AND_RESTORE_MAGNIFICATION_SETTINGS_BUTTONS)
@Test
public void onScreenSizeAndDensityChanged_enabled_restoreSavedMagnifierIndexAndWindow() {
int newSmallestScreenWidthDp =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/back/FlingOnBackAnimationCallbackTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/back/FlingOnBackAnimationCallbackTest.kt
index 75a5768193cf..75a5768193cf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/back/FlingOnBackAnimationCallbackTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/back/FlingOnBackAnimationCallbackTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt
index a4653e736745..77db97710174 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt
@@ -29,7 +29,6 @@ import com.google.common.truth.Truth.assertThat
import org.junit.Test
import platform.test.runner.parameterized.ParameterizedAndroidJunit4
import platform.test.runner.parameterized.Parameters
-import platform.test.runner.parameterized.Parameter
import org.junit.runner.RunWith
@SmallTest
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorTest.kt
index 4d138b488645..5d622eaeb1aa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorTest.kt
@@ -122,9 +122,24 @@ class AudioSharingDeviceItemActionInteractorTest : SysuiTestCase() {
@Test
@DisableFlags(Flags.FLAG_AUDIO_SHARING_QS_DIALOG_IMPROVEMENT)
+ fun testOnClick_connectedAudioSharingMediaDevice_flagOff_previewOn_createDialog() {
+ with(kosmos) {
+ testScope.runTest {
+ whenever(BluetoothUtils.isAudioSharingPreviewEnabled(any())).thenReturn(true)
+ bluetoothTileDialogAudioSharingRepository.setAudioSharingAvailable(true)
+ actionInteractorImpl.onClick(connectedAudioSharingMediaDeviceItem, dialog)
+ verify(dialogTransitionAnimator)
+ .showFromDialog(any(), any(), eq(null), anyBoolean())
+ }
+ }
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_AUDIO_SHARING_QS_DIALOG_IMPROVEMENT)
fun testOnClick_connectedAudioSharingMediaDevice_flagOff_shouldLaunchSettings() {
with(kosmos) {
testScope.runTest {
+ whenever(BluetoothUtils.isAudioSharingPreviewEnabled(any())).thenReturn(false)
bluetoothTileDialogAudioSharingRepository.setAudioSharingAvailable(true)
whenever(cachedBluetoothDevice.device).thenReturn(bluetoothDevice)
actionInteractorImpl.onClick(connectedAudioSharingMediaDeviceItem, dialog)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt
index 2a6d29c61890..2a6d29c61890 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/education/domain/ui/view/ContextualEduDialogTest.kt
index 90727b240caf..90727b240caf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/education/domain/ui/view/ContextualEduDialogTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
index 32fa160fc29f..32fa160fc29f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java
index bf4ef509ac80..eb1b44b75247 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java
@@ -59,6 +59,7 @@ import android.os.Bundle;
import android.os.PowerExemptionManager;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.service.notification.StatusBarNotification;
import android.testing.TestableLooper;
@@ -242,10 +243,14 @@ public class MediaSwitchingControllerTest extends SysuiTestCase {
mLocalMediaManager = spy(mMediaSwitchingController.mLocalMediaManager);
when(mLocalMediaManager.isPreferenceRouteListingExist()).thenReturn(false);
mMediaSwitchingController.mLocalMediaManager = mLocalMediaManager;
+
mMediaSwitchingController.mInputRouteManager =
new InputRouteManager(mContext, mAudioManager);
mInputRouteManager = spy(mMediaSwitchingController.mInputRouteManager);
mMediaSwitchingController.mInputRouteManager = mInputRouteManager;
+ when(mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS))
+ .thenReturn(new AudioDeviceInfo[0]);
+
MediaDescription.Builder builder = new MediaDescription.Builder();
builder.setTitle(TEST_SONG);
builder.setSubtitle(TEST_ARTIST);
@@ -483,11 +488,11 @@ public class MediaSwitchingControllerTest extends SysuiTestCase {
verify(mMediaDevice2, never()).setRangeZone(anyInt());
}
+ @DisableFlags(Flags.FLAG_ENABLE_AUDIO_INPUT_DEVICE_ROUTING_AND_VOLUME_CONTROL)
@Test
public void onDeviceListUpdate_verifyDeviceListCallback() {
// This test relies on mMediaSwitchingController.start being called while the selected
- // device
- // list has exactly one item, and that item's id is:
+ // device list has exactly one item, and that item's id is:
// - Different from both ids in mMediaDevices.
// - Different from the id of the route published by the device under test (usually the
// built-in speakers).
@@ -511,16 +516,54 @@ public class MediaSwitchingControllerTest extends SysuiTestCase {
assertThat(devices.containsAll(mMediaDevices)).isTrue();
assertThat(devices.size()).isEqualTo(mMediaDevices.size());
+ // There should be 2 non-MediaDevice items: the "Speakers & Display" title, and the "Connect
+ // a device" button.
assertThat(mMediaSwitchingController.getMediaItemList().size())
.isEqualTo(mMediaDevices.size() + 2);
verify(mCb).onDeviceListChanged();
}
+ @EnableFlags(Flags.FLAG_ENABLE_AUDIO_INPUT_DEVICE_ROUTING_AND_VOLUME_CONTROL)
+ @Test
+ public void onDeviceListUpdate_verifyDeviceListCallback_inputRouting() {
+ // This test relies on mMediaSwitchingController.start being called while the selected
+ // device list has exactly one item, and that item's id is:
+ // - Different from both ids in mMediaDevices.
+ // - Different from the id of the route published by the device under test (usually the
+ // built-in speakers).
+ // So mock the selected device to respect these two preconditions.
+ MediaDevice mockSelectedMediaDevice = Mockito.mock(MediaDevice.class);
+ when(mockSelectedMediaDevice.getId()).thenReturn(TEST_DEVICE_3_ID);
+ doReturn(List.of(mockSelectedMediaDevice))
+ .when(mLocalMediaManager)
+ .getSelectedMediaDevice();
+
+ mMediaSwitchingController.start(mCb);
+ reset(mCb);
+
+ mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
+ final List<MediaDevice> devices = new ArrayList<>();
+ for (MediaItem item : mMediaSwitchingController.getMediaItemList()) {
+ if (item.getMediaDevice().isPresent()) {
+ devices.add(item.getMediaDevice().get());
+ }
+ }
+
+ assertThat(devices.containsAll(mMediaDevices)).isTrue();
+ assertThat(devices.size()).isEqualTo(mMediaDevices.size());
+ // When input routing is enabled, there should be 4 non-MediaDevice items: one for
+ // the "Output" title, one for the "Speakers & Displays" title, one for the "Connect a
+ // device" button, and one for the "Input" title.
+ assertThat(mMediaSwitchingController.getMediaItemList().size())
+ .isEqualTo(mMediaDevices.size() + 4);
+ verify(mCb).onDeviceListChanged();
+ }
+
+ @DisableFlags(Flags.FLAG_ENABLE_AUDIO_INPUT_DEVICE_ROUTING_AND_VOLUME_CONTROL)
@Test
public void advanced_onDeviceListUpdateWithConnectedDeviceRemote_verifyItemSize() {
// This test relies on mMediaSwitchingController.start being called while the selected
- // device
- // list has exactly one item, and that item's id is:
+ // device list has exactly one item, and that item's id is:
// - Different from both ids in mMediaDevices.
// - Different from the id of the route published by the device under test (usually the
// built-in speakers).
@@ -547,6 +590,7 @@ public class MediaSwitchingControllerTest extends SysuiTestCase {
assertThat(devices.containsAll(mMediaDevices)).isTrue();
assertThat(devices.size()).isEqualTo(mMediaDevices.size());
+ // There should be 1 non-MediaDevice item: the "Speakers & Display" title.
assertThat(mMediaSwitchingController.getMediaItemList().size())
.isEqualTo(mMediaDevices.size() + 1);
verify(mCb).onDeviceListChanged();
@@ -554,6 +598,45 @@ public class MediaSwitchingControllerTest extends SysuiTestCase {
@EnableFlags(Flags.FLAG_ENABLE_AUDIO_INPUT_DEVICE_ROUTING_AND_VOLUME_CONTROL)
@Test
+ public void advanced_onDeviceListUpdateWithConnectedDeviceRemote_verifyItemSize_inputRouting() {
+ // This test relies on mMediaSwitchingController.start being called while the selected
+ // device list has exactly one item, and that item's id is:
+ // - Different from both ids in mMediaDevices.
+ // - Different from the id of the route published by the device under test (usually the
+ // built-in speakers).
+ // So mock the selected device to respect these two preconditions.
+ MediaDevice mockSelectedMediaDevice = Mockito.mock(MediaDevice.class);
+ when(mockSelectedMediaDevice.getId()).thenReturn(TEST_DEVICE_3_ID);
+ doReturn(List.of(mockSelectedMediaDevice))
+ .when(mLocalMediaManager)
+ .getSelectedMediaDevice();
+
+ when(mMediaDevice1.getFeatures())
+ .thenReturn(ImmutableList.of(MediaRoute2Info.FEATURE_REMOTE_PLAYBACK));
+ when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice1);
+ mMediaSwitchingController.start(mCb);
+ reset(mCb);
+
+ mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
+ final List<MediaDevice> devices = new ArrayList<>();
+ for (MediaItem item : mMediaSwitchingController.getMediaItemList()) {
+ if (item.getMediaDevice().isPresent()) {
+ devices.add(item.getMediaDevice().get());
+ }
+ }
+
+ assertThat(devices.containsAll(mMediaDevices)).isTrue();
+ assertThat(devices.size()).isEqualTo(mMediaDevices.size());
+ // When input routing is enabled, there should be 3 non-MediaDevice items: one for
+ // the "Output" title, one for the "Speakers & Displays" title, and one for the "Input"
+ // title.
+ assertThat(mMediaSwitchingController.getMediaItemList().size())
+ .isEqualTo(mMediaDevices.size() + 3);
+ verify(mCb).onDeviceListChanged();
+ }
+
+ @EnableFlags(Flags.FLAG_ENABLE_AUDIO_INPUT_DEVICE_ROUTING_AND_VOLUME_CONTROL)
+ @Test
public void onInputDeviceListUpdate_verifyDeviceListCallback() {
AudioDeviceInfo[] audioDeviceInfos = {};
when(mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt
index fac5ecb49027..f23553eda3b2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt
@@ -23,6 +23,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.click
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.performClick
@@ -73,7 +74,7 @@ class ResizingTest : SysuiTestCase() {
}
@Test
- fun toggleIconTile_shouldBeLarge() {
+ fun toggleIconTileWithA11yAction_shouldBeLarge() {
var tiles by mutableStateOf(TestEditTiles)
val listState = EditTileListState(tiles, columns = 4, largeTilesSpan = 2)
composeRule.setContent {
@@ -89,7 +90,7 @@ class ResizingTest : SysuiTestCase() {
}
@Test
- fun toggleLargeTile_shouldBeIcon() {
+ fun toggleLargeTileWithA11yAction_shouldBeIcon() {
var tiles by mutableStateOf(TestEditTiles)
val listState = EditTileListState(tiles, columns = 4, largeTilesSpan = 2)
composeRule.setContent {
@@ -105,7 +106,7 @@ class ResizingTest : SysuiTestCase() {
}
@Test
- fun resizedLarge_shouldBeIcon() {
+ fun tapOnIconResizingHandle_shouldBeLarge() {
var tiles by mutableStateOf(TestEditTiles)
val listState = EditTileListState(tiles, columns = 4, largeTilesSpan = 2)
composeRule.setContent {
@@ -116,12 +117,32 @@ class ResizingTest : SysuiTestCase() {
composeRule
.onNodeWithContentDescription("tileA")
.performClick() // Select
- .performTouchInput { // Resize down
- swipeRight()
+ .performTouchInput { // Tap on resizing handle
+ click(centerRight)
+ }
+ composeRule.waitForIdle()
+
+ assertThat(tiles.find { it.tile.tileSpec.spec == "tileA" }?.width).isEqualTo(2)
+ }
+
+ @Test
+ fun tapOnLargeResizingHandle_shouldBeIcon() {
+ var tiles by mutableStateOf(TestEditTiles)
+ val listState = EditTileListState(tiles, columns = 4, largeTilesSpan = 2)
+ composeRule.setContent {
+ EditTileGridUnderTest(listState) { spec, toIcon -> tiles = tiles.resize(spec, toIcon) }
+ }
+ composeRule.waitForIdle()
+
+ composeRule
+ .onNodeWithContentDescription("tileD_large")
+ .performClick() // Select
+ .performTouchInput { // Tap on resizing handle
+ click(centerRight)
}
composeRule.waitForIdle()
- assertThat(tiles.find { it.tile.tileSpec.spec == "tileA" }?.width).isEqualTo(1)
+ assertThat(tiles.find { it.tile.tileSpec.spec == "tileD_large" }?.width).isEqualTo(1)
}
@Test
@@ -134,6 +155,26 @@ class ResizingTest : SysuiTestCase() {
composeRule.waitForIdle()
composeRule
+ .onNodeWithContentDescription("tileA")
+ .performClick() // Select
+ .performTouchInput { // Resize up
+ swipeRight(startX = right, endX = right * 2)
+ }
+ composeRule.waitForIdle()
+
+ assertThat(tiles.find { it.tile.tileSpec.spec == "tileA" }?.width).isEqualTo(2)
+ }
+
+ @Test
+ fun resizedLarge_shouldBeIcon() {
+ var tiles by mutableStateOf(TestEditTiles)
+ val listState = EditTileListState(tiles, columns = 4, largeTilesSpan = 2)
+ composeRule.setContent {
+ EditTileGridUnderTest(listState) { spec, toIcon -> tiles = tiles.resize(spec, toIcon) }
+ }
+ composeRule.waitForIdle()
+
+ composeRule
.onNodeWithContentDescription("tileD_large")
.performClick() // Select
.performTouchInput { // Resize down
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
index 859f84edda39..e7fb470cfa76 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -33,6 +33,7 @@ import androidx.lifecycle.LifecycleOwner
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.SceneKey
import com.android.systemui.Flags
+import com.android.systemui.Flags.FLAG_COMMUNAL_HUB_ON_MOBILE
import com.android.systemui.Flags.FLAG_HUBMODE_FULLSCREEN_VERTICAL_SWIPE_FIX
import com.android.systemui.SysuiTestCase
import com.android.systemui.ambient.touch.TouchHandler
@@ -630,6 +631,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
}
}
+ @DisableFlags(FLAG_COMMUNAL_HUB_ON_MOBILE)
@Test
fun onTouchEvent_shadeInteracting_movesNotDispatched() =
with(kosmos) {
@@ -686,6 +688,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
}
}
+ @DisableFlags(FLAG_COMMUNAL_HUB_ON_MOBILE)
@Test
fun onTouchEvent_bouncerInteracting_movesNotDispatched() =
with(kosmos) {
@@ -718,6 +721,19 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
}
}
+ @EnableFlags(FLAG_COMMUNAL_HUB_ON_MOBILE)
+ @Test
+ fun onTouchEvent_onLockscreenAndGlanceableHubV2_touchIgnored() =
+ with(kosmos) {
+ testScope.runTest {
+ // On lockscreen.
+ goToScene(CommunalScenes.Blank)
+
+ assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse()
+ verify(containerView, never()).onTouchEvent(DOWN_EVENT)
+ }
+ }
+
@Test
fun disposeView_destroysTouchMonitor() {
clearInvocations(touchMonitor)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 041d1a611b55..041d1a611b55 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
index 61d14b73204a..58864f635190 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
@@ -76,7 +76,7 @@ import com.android.systemui.statusbar.notification.interruption.NotificationInte
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.FakeEventLog;
import com.android.systemui.util.settings.FakeGlobalSettings;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
index f870200c2b00..28577b336b6e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
@@ -61,7 +61,7 @@ import com.android.systemui.statusbar.notification.logging.nano.Notifications;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.time.FakeSystemClock;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index d8a23d6bd32e..5aee92939ed5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -68,6 +68,7 @@ import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
import com.android.systemui.statusbar.notification.FeedbackIcon;
import com.android.systemui.statusbar.notification.SourceType;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.headsup.PinnedStatus;
import com.android.systemui.statusbar.notification.row.ExpandableView.OnHeightChangedListener;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
import com.android.systemui.statusbar.notification.shared.NotificationContentAlphaOptimization;
@@ -394,7 +395,7 @@ public class ExpandableNotificationRowTest extends SysuiTestCase {
ExpandableNotificationRow row = mNotificationTestHelper.createRow();
AboveShelfChangedListener listener = mock(AboveShelfChangedListener.class);
row.setAboveShelfChangedListener(listener);
- row.setPinned(true);
+ row.setPinnedStatus(PinnedStatus.PinnedBySystem);
verify(listener).onAboveShelfStateChanged(true);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt
index 723c0d701305..2d35ea5d4e95 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt
@@ -65,11 +65,11 @@ import com.android.systemui.statusbar.notification.AssistantFeedbackController
import com.android.systemui.statusbar.notification.NotificationActivityStarter
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider
import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
+import com.android.systemui.statusbar.notification.headsup.headsUpManager
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
import com.android.systemui.statusbar.notificationLockscreenUserManager
import com.android.systemui.statusbar.policy.deviceProvisionedController
-import com.android.systemui.statusbar.policy.headsUpManager
import com.android.systemui.testKosmos
import com.android.systemui.util.kotlin.JavaAdapter
import com.android.systemui.wmshell.BubblesManager
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index d2350bc82667..11b19f95c1c0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -103,7 +103,7 @@ import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrim
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.policy.AvalancheController;
+import com.android.systemui.statusbar.notification.headsup.AvalancheController;
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
import com.android.systemui.wallpapers.domain.interactor.WallpaperInteractor;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index c9f8463d510c..6912eda3c3d4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -198,7 +198,7 @@ import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.ExtensionController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
import com.android.systemui.statusbar.window.StatusBarWindowController;
@@ -334,7 +334,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
@Mock private CentralSurfacesCommandQueueCallbacks mCentralSurfacesCommandQueueCallbacks;
@Mock private PluginManager mPluginManager;
@Mock private ViewMediatorCallback mViewMediatorCallback;
- @Mock private StatusBarTouchableRegionManager mStatusBarTouchableRegionManager;
+ @Mock private ShadeTouchableRegionManager mShadeTouchableRegionManager;
@Mock private PluginDependencyProvider mPluginDependencyProvider;
@Mock private ExtensionController mExtensionController;
@Mock private UserInfoControllerImpl mUserInfoControllerImpl;
@@ -617,7 +617,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
mKeyguardIndicationController,
mDemoModeController,
mNotificationShadeDepthControllerLazy,
- mStatusBarTouchableRegionManager,
+ mShadeTouchableRegionManager,
mBrightnessSliderFactory,
mScreenOffAnimationController,
mWallpaperController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index cace60ce4c0e..2f30b745a4a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -95,7 +95,7 @@ import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
import com.android.systemui.statusbar.notification.row.OnUserInteractionCallback;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModelParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModelParameterizedTest.kt
index 3d7603368ebc..3d7603368ebc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModelParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModelParameterizedTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
index a213c85eaa62..1d4b8e14695d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
@@ -58,6 +58,7 @@ import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
import kotlin.sequences.Sequence;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt
index 1184a76d54ff..1184a76d54ff 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index aad8b4ba1191..af14edd10f5f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -154,7 +154,7 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController;
import com.android.systemui.statusbar.policy.ZenModeController;
@@ -2575,78 +2575,6 @@ public class BubblesTest extends SysuiTestCase {
@EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
@Test
- public void testEventLogging_bubbleBar_dragBarLeft() {
- mBubbleProperties.mIsBubbleBarEnabled = true;
- mPositioner.setIsLargeScreen(true);
- mPositioner.setBubbleBarLocation(BubbleBarLocation.RIGHT);
- FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
- mBubbleController.registerBubbleStateListener(bubbleStateListener);
-
- mEntryListener.onEntryAdded(mRow);
- assertBarMode();
-
- mBubbleController.setBubbleBarLocation(BubbleBarLocation.LEFT,
- BubbleBarLocation.UpdateSource.DRAG_BAR);
-
- verify(mBubbleLogger).log(BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_DRAG_BAR);
- }
-
- @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
- @Test
- public void testEventLogging_bubbleBar_dragBarRight() {
- mBubbleProperties.mIsBubbleBarEnabled = true;
- mPositioner.setIsLargeScreen(true);
- mPositioner.setBubbleBarLocation(BubbleBarLocation.LEFT);
- FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
- mBubbleController.registerBubbleStateListener(bubbleStateListener);
-
- mEntryListener.onEntryAdded(mRow);
- assertBarMode();
-
- mBubbleController.setBubbleBarLocation(BubbleBarLocation.RIGHT,
- BubbleBarLocation.UpdateSource.DRAG_BAR);
-
- verify(mBubbleLogger).log(BubbleLogger.Event.BUBBLE_BAR_MOVED_RIGHT_DRAG_BAR);
- }
-
- @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
- @Test
- public void testEventLogging_bubbleBar_dragBubbleLeft() {
- mBubbleProperties.mIsBubbleBarEnabled = true;
- mPositioner.setIsLargeScreen(true);
- mPositioner.setBubbleBarLocation(BubbleBarLocation.RIGHT);
- FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
- mBubbleController.registerBubbleStateListener(bubbleStateListener);
-
- mEntryListener.onEntryAdded(mRow);
- assertBarMode();
-
- mBubbleController.setBubbleBarLocation(BubbleBarLocation.LEFT,
- BubbleBarLocation.UpdateSource.DRAG_BUBBLE);
-
- verify(mBubbleLogger).log(BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_DRAG_BUBBLE);
- }
-
- @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
- @Test
- public void testEventLogging_bubbleBar_dragBubbleRight() {
- mBubbleProperties.mIsBubbleBarEnabled = true;
- mPositioner.setIsLargeScreen(true);
- mPositioner.setBubbleBarLocation(BubbleBarLocation.LEFT);
- FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
- mBubbleController.registerBubbleStateListener(bubbleStateListener);
-
- mEntryListener.onEntryAdded(mRow);
- assertBarMode();
-
- mBubbleController.setBubbleBarLocation(BubbleBarLocation.RIGHT,
- BubbleBarLocation.UpdateSource.DRAG_BUBBLE);
-
- verify(mBubbleLogger).log(BubbleLogger.Event.BUBBLE_BAR_MOVED_RIGHT_DRAG_BUBBLE);
- }
-
- @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
- @Test
public void testEventLogging_bubbleBar_expandAndCollapse() {
mBubbleProperties.mIsBubbleBarEnabled = true;
mPositioner.setIsLargeScreen(true);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
index 50631409308c..0ba7c8574d85 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt
@@ -62,6 +62,7 @@ import com.android.systemui.statusbar.NotificationShadeDepthController
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator
import com.android.systemui.statusbar.notification.collection.NotifCollection
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
import com.android.systemui.statusbar.notification.logging.NotificationPanelLogger
import com.android.systemui.statusbar.notification.stack.AmbientState
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
@@ -74,7 +75,6 @@ import com.android.systemui.statusbar.phone.ScreenOffAnimationController
import com.android.systemui.statusbar.phone.ScrimController
import com.android.systemui.statusbar.phone.SystemUIDialogManager
import com.android.systemui.statusbar.policy.DeviceProvisionedController
-import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.statusbar.policy.ZenModeController
import com.android.systemui.statusbar.window.StatusBarWindowController
@@ -173,7 +173,7 @@ data class TestMocksModule(
interface Bindings {
@Binds
fun bindStatusBarStateController(
- sysuiStatusBarStateController: SysuiStatusBarStateController,
+ sysuiStatusBarStateController: SysuiStatusBarStateController
): StatusBarStateController
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/MotionEventHelper.java b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/MotionEventHelper.java
index 550e77d63c3b..550e77d63c3b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/MotionEventHelper.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/MotionEventHelper.java
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/TestableWindowManager.java b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/TestableWindowManager.java
index 859517839388..859517839388 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/TestableWindowManager.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/TestableWindowManager.java
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/activity/data/repository/ActivityManagerRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/activity/data/repository/ActivityManagerRepositoryKosmos.kt
new file mode 100644
index 000000000000..a6e71333c816
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/activity/data/repository/ActivityManagerRepositoryKosmos.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.activity.data.repository
+
+import android.app.activityManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.log.core.Logger
+import kotlinx.coroutines.flow.MutableStateFlow
+
+val Kosmos.activityManagerRepository by Kosmos.Fixture { FakeActivityManagerRepository() }
+
+val Kosmos.realActivityManagerRepository by
+ Kosmos.Fixture { ActivityManagerRepositoryImpl(testDispatcher, activityManager) }
+
+class FakeActivityManagerRepository : ActivityManagerRepository {
+ private val uidFlows = mutableMapOf<Int, MutableList<MutableStateFlow<Boolean>>>()
+
+ var startingIsAppVisibleValue = false
+
+ override fun createIsAppVisibleFlow(
+ creationUid: Int,
+ logger: Logger,
+ identifyingLogTag: String,
+ ): MutableStateFlow<Boolean> {
+ val newFlow = MutableStateFlow(startingIsAppVisibleValue)
+ uidFlows.computeIfAbsent(creationUid) { mutableListOf() }.add(newFlow)
+ return newFlow
+ }
+
+ fun setIsAppVisible(uid: Int, isAppVisible: Boolean) {
+ uidFlows[uid]?.forEach { stateFlow -> stateFlow.value = isAppVisible }
+ }
+}
+
+val ActivityManagerRepository.fake
+ get() = this as FakeActivityManagerRepository
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorKosmos.kt
index 4f4d1da42303..e0c0fbd7f033 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bluetooth/qsdialog/AudioSharingInteractorKosmos.kt
@@ -16,12 +16,14 @@
package com.android.systemui.bluetooth.qsdialog
+import android.content.applicationContext
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testDispatcher
val Kosmos.audioSharingInteractor: AudioSharingInteractor by
Kosmos.Fixture {
AudioSharingInteractorImpl(
+ applicationContext,
localBluetoothManager,
bluetoothTileDialogAudioSharingRepository,
testDispatcher,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
index 78ea70086605..ddcc92691f5b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
@@ -57,6 +57,10 @@ class FakeDisplayRepository @Inject constructor() : DisplayRepository {
addDisplay(display(type, id = displayId))
}
+ suspend fun addDisplays(vararg displays: Display) {
+ displays.forEach { addDisplay(it) }
+ }
+
suspend fun addDisplay(display: Display) {
flow.value += display
displayAdditionEventFlow.emit(display)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/doze/DozeServiceFake.java b/packages/SystemUI/tests/utils/src/com/android/systemui/doze/DozeServiceFake.java
index f55c2b77ca0f..f55c2b77ca0f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/doze/DozeServiceFake.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/doze/DozeServiceFake.java
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModelKosmos.kt
index b24b3ad05117..71746b505a48 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModelKosmos.kt
@@ -16,6 +16,7 @@
package com.android.systemui.dreams.ui.viewmodel
+import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.shade.domain.interactor.shadeInteractor
@@ -23,6 +24,7 @@ import com.android.systemui.shade.domain.interactor.shadeInteractor
val Kosmos.dreamUserActionsViewModel by
Kosmos.Fixture {
DreamUserActionsViewModel(
+ communalInteractor = communalInteractor,
deviceUnlockedInteractor = deviceUnlockedInteractor,
shadeInteractor = shadeInteractor,
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
index 721c0b8339db..2c8581608739 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
@@ -102,10 +102,7 @@ val Kosmos.defaultShortcutCategoriesRepository by
)
}
-val Kosmos.inputGestureMaps by
- Kosmos.Fixture {
- InputGestureMaps(applicationContext)
- }
+val Kosmos.inputGestureMaps by Kosmos.Fixture { InputGestureMaps(applicationContext) }
val Kosmos.customShortcutCategoriesRepository by
Kosmos.Fixture {
@@ -116,7 +113,7 @@ val Kosmos.customShortcutCategoriesRepository by
testDispatcher,
shortcutCategoriesUtils,
applicationContext,
- inputGestureMaps
+ inputGestureMaps,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt
index 548b5646f5d4..548b5646f5d4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractorKosmos.kt
index 007d2297e387..f88ed07046ab 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractorKosmos.kt
@@ -16,18 +16,24 @@
package com.android.systemui.keyguard.domain.interactor
+import com.android.internal.widget.lockPatternUtils
import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
import com.android.systemui.keyguard.data.repository.keyguardRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.user.domain.interactor.selectedUserInteractor
val Kosmos.keyguardEnabledInteractor by
Kosmos.Fixture {
KeyguardEnabledInteractor(
applicationCoroutineScope,
+ testDispatcher,
keyguardRepository,
biometricSettingsRepository,
- keyguardDismissTransitionInteractor,
+ selectedUserInteractor,
+ lockPatternUtils,
+ { keyguardDismissTransitionInteractor },
internalTransitionInteractor = internalKeyguardTransitionInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLockWhileAwakeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLockWhileAwakeInteractorKosmos.kt
index 39236c7c9fae..2423949cdacc 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLockWhileAwakeInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLockWhileAwakeInteractorKosmos.kt
@@ -24,5 +24,6 @@ val Kosmos.keyguardLockWhileAwakeInteractor by
KeyguardLockWhileAwakeInteractor(
biometricSettingsRepository = biometricSettingsRepository,
keyguardEnabledInteractor = keyguardEnabledInteractor,
+ keyguardServiceLockNowInteractor = keyguardServiceLockNowInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardServiceLockNowInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardServiceLockNowInteractor.kt
new file mode 100644
index 000000000000..29335c590a75
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardServiceLockNowInteractor.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+
+val Kosmos.keyguardServiceLockNowInteractor by
+ Kosmos.Fixture { KeyguardServiceLockNowInteractor(backgroundScope = testScope) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorKosmos.kt
index 63e168d018bf..4aa132c1d3af 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardWakeDirectlyToGoneInteractorKosmos.kt
@@ -41,5 +41,7 @@ val Kosmos.keyguardWakeDirectlyToGoneInteractor by
lockPatternUtils,
fakeSettings,
selectedUserInteractor,
+ keyguardEnabledInteractor,
+ keyguardServiceLockNowInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSTile.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSTile.kt
index 562980d43485..06822a6c2339 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSTile.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSTile.kt
@@ -19,10 +19,12 @@ package com.android.systemui.qs
import com.android.internal.logging.InstanceId
import com.android.systemui.animation.Expandable
import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.plugins.qs.TileDetailsViewModel
class FakeQSTile(var user: Int, var available: Boolean = true) : QSTile {
private var tileSpec: String? = null
var destroyed = false
+ var hasDetailsViewModel: Boolean = true
private var state = QSTile.State()
val callbacks = mutableListOf<QSTile.Callback>()
@@ -91,6 +93,13 @@ class FakeQSTile(var user: Int, var available: Boolean = true) : QSTile {
return false
}
+ override fun getDetailsViewModel(): FakeTileDetailsViewModel? {
+ if (hasDetailsViewModel) {
+ return FakeTileDetailsViewModel(tileSpec)
+ }
+ return null
+ }
+
fun changeState(newState: QSTile.State) {
state = newState
callbacks.forEach { it.onStateChanged(state) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeTileDetailsViewModel.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeTileDetailsViewModel.kt
new file mode 100644
index 000000000000..555f019822c2
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeTileDetailsViewModel.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs
+
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import com.android.systemui.plugins.qs.TileDetailsViewModel
+
+class FakeTileDetailsViewModel(var tileSpec: String?) : TileDetailsViewModel() {
+ private var _clickOnSettingsButton = 0
+
+ @Composable
+ override fun GetContentView() {
+ Text(
+ text = "Fake details content",
+ textAlign = TextAlign.Center,
+ fontWeight = FontWeight.ExtraBold,
+ )
+ }
+
+ override fun clickOnSettingsButton() {
+ _clickOnSettingsButton++
+ }
+
+ override fun getTitle(): String {
+ return tileSpec ?: " Fake title"
+ }
+
+ override fun getSubTitle(): String {
+ return tileSpec ?: "Fake sub title"
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt
index b5a6bf126719..6fe860cfd0d3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt
@@ -19,12 +19,14 @@ package com.android.systemui.qs.panels.domain.interactor
import com.android.systemui.haptics.msdl.tileHapticsViewModelFactoryProvider
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.qs.panels.ui.compose.infinitegrid.InfiniteGridLayout
+import com.android.systemui.qs.panels.ui.viewmodel.detailsViewModel
import com.android.systemui.qs.panels.ui.viewmodel.iconTilesViewModel
import com.android.systemui.qs.panels.ui.viewmodel.infiniteGridViewModelFactory
val Kosmos.infiniteGridLayout by
Kosmos.Fixture {
InfiniteGridLayout(
+ detailsViewModel,
iconTilesViewModel,
infiniteGridViewModelFactory,
tileHapticsViewModelFactoryProvider,
diff --git a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodConfigState.java b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModelKosmos.kt
index 7d3d8b9f7974..dc22905ba320 100644
--- a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodConfigState.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModelKosmos.kt
@@ -13,10 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.platform.test.ravenwood;
-/** Stub class. The actual implementation is in junit-impl-src. */
-public class RavenwoodConfigState {
- public RavenwoodConfigState(RavenwoodConfig config) {
- }
-}
+package com.android.systemui.qs.panels.ui.viewmodel
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor
+
+val Kosmos.detailsViewModel by Kosmos.Fixture { DetailsViewModel(currentTilesInteractor) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapterKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapterKosmos.kt
index a90876551d20..de9f629ef787 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapterKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapterKosmos.kt
@@ -18,6 +18,7 @@ package com.android.systemui.qs.tiles.viewmodel
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.util.mockito.mock
val Kosmos.qsTileViewModelAdaperFactory by
@@ -28,6 +29,7 @@ val Kosmos.qsTileViewModelAdaperFactory by
applicationCoroutineScope,
mock(),
qsTileViewModel,
+ testDispatcher,
)
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModelKosmos.kt
index ce103ec57a86..6afc0d803f8d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModelKosmos.kt
@@ -18,6 +18,7 @@ package com.android.systemui.qs.ui.viewmodel
import com.android.systemui.brightness.ui.viewmodel.brightnessSliderViewModelFactory
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.panels.ui.viewmodel.detailsViewModel
import com.android.systemui.qs.panels.ui.viewmodel.editModeViewModel
import com.android.systemui.qs.panels.ui.viewmodel.quickQuickSettingsViewModelFactory
import com.android.systemui.qs.panels.ui.viewmodel.tileGridViewModel
@@ -34,6 +35,7 @@ val Kosmos.quickSettingsContainerViewModelFactory by
supportsBrightnessMirroring,
tileGridViewModel,
editModeViewModel,
+ detailsViewModel,
)
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryKosmos.kt
new file mode 100644
index 000000000000..dbaa0b1d5bf6
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryKosmos.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.data.repository
+
+import android.view.Display
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.shade.display.ShadeDisplayPolicy
+import com.android.systemui.shade.display.SpecificDisplayIdPolicy
+
+val Kosmos.defaultShadeDisplayPolicy: ShadeDisplayPolicy by
+ Kosmos.Fixture { SpecificDisplayIdPolicy(Display.DEFAULT_DISPLAY) }
+
+val Kosmos.shadeDisplaysRepository: MutableShadeDisplaysRepository by
+ Kosmos.Fixture {
+ ShadeDisplaysRepositoryImpl(
+ defaultPolicy = defaultShadeDisplayPolicy,
+ bgScope = testScope.backgroundScope,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModelKosmos.kt
index 7097d3130aa0..694bb6e87ef6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModelKosmos.kt
@@ -26,6 +26,7 @@ import com.android.systemui.qs.ui.adapter.qsSceneAdapter
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.settings.brightness.ui.viewmodel.brightnessMirrorViewModelFactory
import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.disableflags.domain.interactor.disableFlagsInteractor
import com.android.systemui.unfold.domain.interactor.unfoldTransitionInteractor
val Kosmos.shadeSceneContentViewModel: ShadeSceneContentViewModel by Fixture {
@@ -35,6 +36,7 @@ val Kosmos.shadeSceneContentViewModel: ShadeSceneContentViewModel by Fixture {
brightnessMirrorViewModelFactory = brightnessMirrorViewModelFactory,
mediaCarouselInteractor = mediaCarouselInteractor,
shadeInteractor = shadeInteractor,
+ disableFlagsInteractor = disableFlagsInteractor,
footerActionsViewModelFactory = footerActionsViewModelFactory,
footerActionsController = footerActionsController,
unfoldTransitionInteractor = unfoldTransitionInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorFactoryKosmos.kt
new file mode 100644
index 000000000000..1c095e11dffa
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorFactoryKosmos.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.chips.notification.domain.interactor
+
+import com.android.systemui.activity.data.repository.activityManagerRepository
+import com.android.systemui.activity.data.repository.fake
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.chips.statusBarChipsLogger
+
+val Kosmos.singleNotificationChipInteractorFactory: SingleNotificationChipInteractor.Factory by
+ Kosmos.Fixture {
+ SingleNotificationChipInteractor.Factory { startingModel ->
+ SingleNotificationChipInteractor(
+ startingModel,
+ activityManagerRepository.fake,
+ logBuffer = statusBarChipsLogger,
+ )
+ }
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorKosmos.kt
index 74c7611a6392..03e9f3d52ca3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorKosmos.kt
@@ -17,6 +17,16 @@
package com.android.systemui.statusbar.chips.notification.domain.interactor
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.chips.statusBarChipsLogger
+import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
val Kosmos.statusBarNotificationChipsInteractor: StatusBarNotificationChipsInteractor by
- Kosmos.Fixture { StatusBarNotificationChipsInteractor() }
+ Kosmos.Fixture {
+ StatusBarNotificationChipsInteractor(
+ testScope.backgroundScope,
+ activeNotificationsInteractor,
+ singleNotificationChipInteractorFactory,
+ logBuffer = statusBarChipsLogger,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelKosmos.kt
index 68b28adb4b3a..4bcce8601d64 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelKosmos.kt
@@ -19,13 +19,8 @@ package com.android.systemui.statusbar.chips.notification.ui.viewmodel
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.statusbar.chips.notification.domain.interactor.statusBarNotificationChipsInteractor
-import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
val Kosmos.notifChipsViewModel: NotifChipsViewModel by
Kosmos.Fixture {
- NotifChipsViewModel(
- applicationCoroutineScope,
- activeNotificationsInteractor,
- statusBarNotificationChipsInteractor,
- )
+ NotifChipsViewModel(applicationCoroutineScope, statusBarNotificationChipsInteractor)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/FakeHeadsUpRowRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/FakeHeadsUpRowRepository.kt
index 980d65fde6de..7de22d8c8b43 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/FakeHeadsUpRowRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/FakeHeadsUpRowRepository.kt
@@ -16,13 +16,27 @@
package com.android.systemui.statusbar.notification.data.repository
+import com.android.systemui.statusbar.notification.headsup.PinnedStatus
import kotlinx.coroutines.flow.MutableStateFlow
class FakeHeadsUpRowRepository(override val key: String, override val elementKey: Any = Any()) :
HeadsUpRowRepository {
- constructor(key: String, isPinned: Boolean) : this(key = key) {
- this.isPinned.value = isPinned
+ constructor(
+ key: String,
+ elementKey: Any = Any(),
+ isPinned: Boolean,
+ ) : this(key = key, elementKey = elementKey) {
+ this.pinnedStatus.value = if (isPinned) PinnedStatus.PinnedBySystem else PinnedStatus.NotPinned
}
- override val isPinned: MutableStateFlow<Boolean> = MutableStateFlow(false)
+ constructor(
+ key: String,
+ elementKey: Any = Any(),
+ pinnedStatus: PinnedStatus,
+ ) : this(key = key, elementKey = elementKey) {
+ this.pinnedStatus.value = pinnedStatus
+ }
+
+ override val pinnedStatus: MutableStateFlow<PinnedStatus> =
+ MutableStateFlow(PinnedStatus.NotPinned)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/HeadsUpManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerKosmos.kt
index a4db00c9b6ae..de9485d48383 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/HeadsUpManagerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerKosmos.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.policy
+package com.android.systemui.statusbar.notification.headsup
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/FakePromotedNotificationsProvider.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/FakePromotedNotificationsProvider.kt
new file mode 100644
index 000000000000..88caf6e2ba30
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/FakePromotedNotificationsProvider.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.promoted
+
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+
+class FakePromotedNotificationsProvider : PromotedNotificationsProvider {
+ val promotedEntries = mutableSetOf<NotificationEntry>()
+
+ override fun shouldPromote(entry: NotificationEntry): Boolean {
+ return promotedEntries.contains(entry)
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorKosmos.kt
new file mode 100644
index 000000000000..5e9f12b4b1cc
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorKosmos.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.promoted
+
+import android.content.applicationContext
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.promotedNotificationContentExtractor by
+ Kosmos.Fixture {
+ PromotedNotificationContentExtractor(
+ promotedNotificationsProvider,
+ applicationContext,
+ promotedNotificationLogger,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLoggerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLoggerKosmos.kt
new file mode 100644
index 000000000000..2805d1e02356
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLoggerKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.promoted
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.log.logcatLogBuffer
+
+val Kosmos.promotedNotificationLogger by
+ Kosmos.Fixture { PromotedNotificationLogger(logcatLogBuffer("PromotedNotifLog")) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsProviderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsProviderKosmos.kt
index a7aa0b41a7aa..580f617a3cdf 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsProviderKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsProviderKosmos.kt
@@ -18,4 +18,5 @@ package com.android.systemui.statusbar.notification.promoted
import com.android.systemui.kosmos.Kosmos
-val Kosmos.promotedNotificationsProvider by Kosmos.Fixture { PromotedNotificationsProviderImpl() }
+var Kosmos.promotedNotificationsProvider: PromotedNotificationsProvider by
+ Kosmos.Fixture { PromotedNotificationsProviderImpl() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt
index 31d390862540..2d3f68faf4b7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt
@@ -36,6 +36,7 @@ import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.media.controls.util.MediaFeatureFlag
import com.android.systemui.media.dialog.MediaOutputDialogManager
import com.android.systemui.plugins.ActivityStarter
@@ -60,9 +61,13 @@ import com.android.systemui.statusbar.notification.collection.render.GroupExpans
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManagerImpl
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManagerImpl
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager
import com.android.systemui.statusbar.notification.icon.IconBuilder
import com.android.systemui.statusbar.notification.icon.IconManager
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractor
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationLogger
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationsProviderImpl
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.CoordinateOnClickListener
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.ExpandableNotificationRowLogger
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.OnExpandClickListener
@@ -77,7 +82,6 @@ import com.android.systemui.statusbar.notification.row.shared.NotificationRowCon
import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainerLogger
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.phone.KeyguardDismissUtil
-import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.statusbar.policy.SmartActionInflaterImpl
import com.android.systemui.statusbar.policy.SmartReplyConstants
import com.android.systemui.statusbar.policy.SmartReplyInflaterImpl
@@ -221,6 +225,17 @@ class ExpandableNotificationRowBuilder(
Mockito.mock(ConversationNotificationManager::class.java, STUB_ONLY),
)
+ val promotedNotificationsProvider = PromotedNotificationsProviderImpl()
+ val promotedNotificationLog = logcatLogBuffer("PromotedNotifLog")
+ val promotedNotificationLogger = PromotedNotificationLogger(promotedNotificationLog)
+
+ val promotedNotificationContentExtractor =
+ PromotedNotificationContentExtractor(
+ promotedNotificationsProvider,
+ context,
+ promotedNotificationLogger,
+ )
+
mContentBinder =
if (NotificationRowContentBinderRefactor.isEnabled)
NotificationRowContentBinderImpl(
@@ -231,6 +246,7 @@ class ExpandableNotificationRowBuilder(
smartReplyStateInflater,
notifLayoutInflaterFactoryProvider,
Mockito.mock(HeadsUpStyleProvider::class.java, STUB_ONLY),
+ promotedNotificationContentExtractor,
Mockito.mock(NotificationRowContentBinderLogger::class.java, STUB_ONLY),
)
else
@@ -243,6 +259,7 @@ class ExpandableNotificationRowBuilder(
smartReplyStateInflater,
notifLayoutInflaterFactoryProvider,
Mockito.mock(HeadsUpStyleProvider::class.java, STUB_ONLY),
+ promotedNotificationContentExtractor,
Mockito.mock(NotificationRowContentBinderLogger::class.java, STUB_ONLY),
)
mContentBinder.setInflateSynchronously(true)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/OnUserInteractionCallbackKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/OnUserInteractionCallbackKosmos.kt
index 1e3897ba46c6..ec54c33f1156 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/OnUserInteractionCallbackKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/OnUserInteractionCallbackKosmos.kt
@@ -22,7 +22,7 @@ import com.android.systemui.statusbar.notification.collection.coordinator.visual
import com.android.systemui.statusbar.notification.collection.inflation.OnUserInteractionCallbackImpl
import com.android.systemui.statusbar.notification.collection.notifCollection
import com.android.systemui.statusbar.notification.collection.render.notificationVisibilityProvider
-import com.android.systemui.statusbar.policy.headsUpManager
+import com.android.systemui.statusbar.notification.headsup.headsUpManager
var Kosmos.onUserInteractionCallback: OnUserInteractionCallback by
Kosmos.Fixture {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmKosmos.kt
index e20ce270fc14..a5c4bfd61945 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmKosmos.kt
@@ -18,7 +18,7 @@ package com.android.systemui.statusbar.notification.stack
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.statusbar.policy.AvalancheController
+import com.android.systemui.statusbar.notification.headsup.AvalancheController
import com.android.systemui.util.mockito.mock
var Kosmos.stackScrollAlgorithmSectionProvider by Fixture {
@@ -29,6 +29,4 @@ var Kosmos.stackScrollAlgorithmBypassController by Fixture {
mock<StackScrollAlgorithm.BypassController>()
}
-var Kosmos.avalancheController by Fixture {
- mock<AvalancheController>()
-}
+var Kosmos.avalancheController by Fixture { mock<AvalancheController>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt
index 9c3f510c8585..e972c2c77902 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt
@@ -25,7 +25,7 @@ import com.android.systemui.scene.data.repository.windowRootViewVisibilityReposi
import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
-import com.android.systemui.statusbar.policy.headsUpManager
+import com.android.systemui.statusbar.notification.headsup.headsUpManager
val Kosmos.windowRootViewVisibilityInteractor by Fixture {
WindowRootViewVisibilityInteractor(
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/DozeServiceHostKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/DozeServiceHostKosmos.kt
index 4a6757db9481..f86824a1af6d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/DozeServiceHostKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/DozeServiceHostKosmos.kt
@@ -26,10 +26,10 @@ import com.android.systemui.keyguard.wakefulnessLifecycle
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.plugins.statusbar.statusBarStateController
import com.android.systemui.shade.domain.interactor.shadeLockscreenInteractor
+import com.android.systemui.statusbar.notification.headsup.headsUpManager
import com.android.systemui.statusbar.notificationShadeWindowController
import com.android.systemui.statusbar.policy.batteryController
import com.android.systemui.statusbar.policy.deviceProvisionedController
-import com.android.systemui.statusbar.policy.headsUpManager
import com.android.systemui.statusbar.pulseExpansionHandler
import kotlinx.coroutines.ExperimentalCoroutinesApi
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ShadeTouchableRegionManagerKosmos.kt
index 87ea1473bba1..5b7f23b0cff2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManagerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ShadeTouchableRegionManagerKosmos.kt
@@ -24,16 +24,15 @@ import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.notification.headsup.headsUpManager
import com.android.systemui.statusbar.notificationShadeWindowController
import com.android.systemui.statusbar.policy.configurationController
-import com.android.systemui.statusbar.policy.headsUpManager
import com.android.systemui.util.kotlin.JavaAdapter
import com.android.systemui.util.mockito.mock
-import org.mockito.Mockito.mock
-var Kosmos.statusBarTouchableRegionManager by
+var Kosmos.shadeTouchableRegionManager by
Kosmos.Fixture {
- StatusBarTouchableRegionManager(
+ ShadeTouchableRegionManager(
applicationContext,
notificationShadeWindowController,
configurationController,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt
index c6684af9d5eb..6083414240a6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterKosmos.kt
@@ -36,6 +36,7 @@ import com.android.systemui.shade.shadeController
import com.android.systemui.statusbar.commandQueue
import com.android.systemui.statusbar.notification.collection.provider.launchFullScreenIntentProvider
import com.android.systemui.statusbar.notification.collection.render.notificationVisibilityProvider
+import com.android.systemui.statusbar.notification.headsup.headsUpManager
import com.android.systemui.statusbar.notification.notificationTransitionAnimatorControllerProvider
import com.android.systemui.statusbar.notification.row.onUserInteractionCallback
import com.android.systemui.statusbar.notificationClickNotifier
@@ -43,7 +44,6 @@ import com.android.systemui.statusbar.notificationLockscreenUserManager
import com.android.systemui.statusbar.notificationPresenter
import com.android.systemui.statusbar.notificationRemoteInputManager
import com.android.systemui.statusbar.notificationShadeWindowController
-import com.android.systemui.statusbar.policy.headsUpManager
import com.android.systemui.statusbar.policy.keyguardStateController
import com.android.systemui.wmshell.bubblesManager
import java.util.Optional
diff --git a/packages/Vcn/service-b/Android.bp b/packages/Vcn/service-b/Android.bp
index a462297c07af..03ef4e67af1c 100644
--- a/packages/Vcn/service-b/Android.bp
+++ b/packages/Vcn/service-b/Android.bp
@@ -19,6 +19,19 @@ package {
default_applicable_licenses: ["Android-Apache-2.0"],
}
+filegroup {
+ name: "vcn-location-sources",
+ srcs: select(release_flag("RELEASE_MOVE_VCN_TO_MAINLINE"), {
+ true: [
+ "vcn-location-flag/module/com/android/server/vcn/VcnLocation.java",
+ ],
+ default: [
+ "vcn-location-flag/platform/com/android/server/vcn/VcnLocation.java",
+ ],
+ }),
+ visibility: ["//frameworks/base/services/core"],
+}
+
java_library {
name: "service-connectivity-b-pre-jarjar",
sdk_version: "system_server_current",
diff --git a/packages/Vcn/service-b/vcn-location-flag/module/com/android/server/vcn/VcnLocation.java b/packages/Vcn/service-b/vcn-location-flag/module/com/android/server/vcn/VcnLocation.java
new file mode 100644
index 000000000000..6c7d24db8bac
--- /dev/null
+++ b/packages/Vcn/service-b/vcn-location-flag/module/com/android/server/vcn/VcnLocation.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vcn;
+
+/**
+ * Class to represent that VCN is in a mainline module
+ *
+ * <p>This class is used to check whether VCN is in the non-updatable platform or in a mainline
+ * module.
+ */
+// When VCN is in a mainline module, this class (module/com/android/server/vcn/VcnLocation.java)
+// will be built in to the vcn-location-sources filegroup. When VCN is in the non-updatable
+// platform, platform/com/android/server/vcn/VcnLocation.java will be built in to the filegroup
+public class VcnLocation {
+ /** Indicate that VCN is the platform */
+ public static final boolean IS_VCN_IN_MAINLINE = true;
+}
diff --git a/packages/Vcn/service-b/vcn-location-flag/platform/com/android/server/vcn/VcnLocation.java b/packages/Vcn/service-b/vcn-location-flag/platform/com/android/server/vcn/VcnLocation.java
new file mode 100644
index 000000000000..c6c82a55de0f
--- /dev/null
+++ b/packages/Vcn/service-b/vcn-location-flag/platform/com/android/server/vcn/VcnLocation.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vcn;
+
+/**
+ * Class to represent that VCN is in the platform
+ *
+ * <p>This class is used to check whether VCN is in the non-updatable platform or in a mainline
+ * module.
+ */
+// When VCN is in a mainline module, module/com/android/server/vcn/VcnLocation.java
+// will be built in to the vcn-location-sources filegroup. When VCN is in the non-updatable
+// platform, this class (platform/com/android/server/vcn/VcnLocation.java) will be built in to the
+// filegroup
+public class VcnLocation {
+ /** Indicate that VCN is the platform */
+ public static final boolean IS_VCN_IN_MAINLINE = false;
+}
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
index 9b71f8050c80..de3c5f2c23ab 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
@@ -133,9 +133,6 @@ public final class RavenwoodAwareTestRunner extends RavenwoodAwareTestRunnerBase
Log.v(TAG, "RavenwoodAwareTestRunner starting for " + testClass.getCanonicalName());
- // This is needed to make AndroidJUnit4ClassRunner happy.
- InstrumentationRegistry.registerInstance(null, Bundle.EMPTY);
-
// Hook point to allow more customization.
runAnnotatedMethodsOnRavenwood(RavenwoodTestRunnerInitializing.class, null);
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodConfigState.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodConfigState.java
deleted file mode 100644
index 870a10a1f57e..000000000000
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodConfigState.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.platform.test.ravenwood;
-
-import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_EMPTY_RESOURCES_APK;
-
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
-import android.annotation.Nullable;
-import android.app.ResourcesManager;
-import android.content.res.Resources;
-import android.view.DisplayAdjustments;
-
-import java.io.File;
-import java.util.HashMap;
-
-/**
- * Used to store various states associated with {@link RavenwoodConfig} that's inly needed
- * in junit-impl.
- *
- * We don't want to put it in junit-src to avoid having to recompile all the downstream
- * dependencies after changing this class.
- *
- * All members must be called from the runner's main thread.
- */
-public class RavenwoodConfigState {
- private static final String TAG = "RavenwoodConfigState";
-
- private final RavenwoodConfig mConfig;
-
- // TODO: Move the other contexts from RavenwoodConfig to here too? They're used by
- // RavenwoodRule too, but RavenwoodRule can probably use InstrumentationRegistry?
- RavenwoodContext mSystemServerContext;
-
- public RavenwoodConfigState(RavenwoodConfig config) {
- mConfig = config;
- }
-
- /** Map from path -> resources. */
- private final HashMap<File, Resources> mCachedResources = new HashMap<>();
-
- /**
- * Load {@link Resources} from an APK, with cache.
- */
- public Resources loadResources(@Nullable File apkPath) {
- var cached = mCachedResources.get(apkPath);
- if (cached != null) {
- return cached;
- }
-
- var fileToLoad = apkPath != null ? apkPath : new File(RAVENWOOD_EMPTY_RESOURCES_APK);
-
- assertTrue("File " + fileToLoad + " doesn't exist.", fileToLoad.isFile());
-
- final String path = fileToLoad.getAbsolutePath();
- final var emptyPaths = new String[0];
-
- ResourcesManager.getInstance().initializeApplicationPaths(path, emptyPaths);
-
- final var ret = ResourcesManager.getInstance().getResources(null, path,
- emptyPaths, emptyPaths, emptyPaths,
- emptyPaths, null, null,
- new DisplayAdjustments().getCompatibilityInfo(),
- RavenwoodRuntimeEnvironmentController.class.getClassLoader(), null);
-
- assertNotNull(ret);
-
- mCachedResources.put(apkPath, ret);
- return ret;
- }
-}
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunnerState.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunnerState.java
index ec00e8fea887..6dfcf4ce03cf 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunnerState.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRunnerState.java
@@ -15,24 +15,23 @@
*/
package android.platform.test.ravenwood;
-import static com.android.ravenwood.common.RavenwoodCommonUtils.ensureIsPublicMember;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.annotation.Nullable;
import android.util.Log;
+import android.util.Pair;
-import com.android.ravenwood.common.RavenwoodRuntimeException;
+import com.android.ravenwood.RavenwoodRuntimeNative;
-import org.junit.ClassRule;
-import org.junit.Rule;
-import org.junit.rules.TestRule;
import org.junit.runner.Description;
-import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
/**
- * Used to store various states associated with the current test runner that's inly needed
+ * Used to store various states associated with the current test runner that's only needed
* in junit-impl.
*
* We don't want to put it in junit-src to avoid having to recompile all the downstream
@@ -42,6 +41,11 @@ import java.lang.reflect.Field;
*/
public final class RavenwoodRunnerState {
private static final String TAG = "RavenwoodRunnerState";
+ private static final String RAVENWOOD_RULE_ERROR =
+ "RavenwoodRule(s) are not executed in the correct order";
+
+ private static final List<Pair<RavenwoodRule, RavenwoodPropertyState>> sActiveProperties =
+ new ArrayList<>();
private final RavenwoodAwareTestRunner mRunner;
@@ -52,207 +56,95 @@ public final class RavenwoodRunnerState {
mRunner = runner;
}
- /**
- * The RavenwoodConfig used to configure the current Ravenwood environment.
- * This can either come from mConfig or mRule.
- */
- private RavenwoodConfig mCurrentConfig;
- /**
- * The RavenwoodConfig declared in the test class
- */
- private RavenwoodConfig mConfig;
- /**
- * The RavenwoodRule currently in effect, declared in the test class
- */
- private RavenwoodRule mRule;
- private boolean mHasRavenwoodRule;
private Description mMethodDescription;
- public RavenwoodConfig getConfig() {
- return mCurrentConfig;
- }
-
public void enterTestRunner() {
Log.i(TAG, "enterTestRunner: " + mRunner);
-
- mHasRavenwoodRule = hasRavenwoodRule(mRunner.mTestJavaClass);
- mConfig = extractConfiguration(mRunner.mTestJavaClass);
-
- if (mConfig != null) {
- if (mHasRavenwoodRule) {
- fail("RavenwoodConfig and RavenwoodRule cannot be used in the same class."
- + " Suggest migrating to RavenwoodConfig.");
- }
- mCurrentConfig = mConfig;
- } else if (!mHasRavenwoodRule) {
- // If no RavenwoodConfig and no RavenwoodRule, use a default config
- mCurrentConfig = new RavenwoodConfig.Builder().build();
- }
-
- if (mCurrentConfig != null) {
- RavenwoodRuntimeEnvironmentController.init(mRunner);
- }
+ RavenwoodRuntimeEnvironmentController.initForRunner();
}
public void enterTestClass() {
Log.i(TAG, "enterTestClass: " + mRunner.mTestJavaClass.getName());
-
- if (mCurrentConfig != null) {
- RavenwoodRuntimeEnvironmentController.init(mRunner);
- }
}
public void exitTestClass() {
Log.i(TAG, "exitTestClass: " + mRunner.mTestJavaClass.getName());
- try {
- if (mCurrentConfig != null) {
- RavenwoodRuntimeEnvironmentController.reset();
- }
- } finally {
- mConfig = null;
- mRule = null;
- }
+ assertTrue(RAVENWOOD_RULE_ERROR, sActiveProperties.isEmpty());
+ RavenwoodRuntimeEnvironmentController.exitTestClass();
}
public void enterTestMethod(Description description) {
mMethodDescription = description;
+ RavenwoodRuntimeEnvironmentController.initForMethod();
}
public void exitTestMethod() {
mMethodDescription = null;
- RavenwoodRuntimeEnvironmentController.reinit();
}
public void enterRavenwoodRule(RavenwoodRule rule) {
- if (!mHasRavenwoodRule) {
- fail("If you have a RavenwoodRule in your test, make sure the field type is"
- + " RavenwoodRule so Ravenwood can detect it.");
- }
- if (mRule != null) {
- fail("Multiple nesting RavenwoodRule's are detected in the same class,"
- + " which is not supported.");
- }
- mRule = rule;
- if (mCurrentConfig == null) {
- mCurrentConfig = rule.getConfiguration();
- }
- RavenwoodRuntimeEnvironmentController.init(mRunner);
+ pushTestProperties(rule);
}
public void exitRavenwoodRule(RavenwoodRule rule) {
- if (mRule != rule) {
- fail("RavenwoodRule did not take effect.");
- }
- mRule = null;
+ popTestProperties(rule);
}
- /**
- * @return a configuration from a test class, if any.
- */
- @Nullable
- private static RavenwoodConfig extractConfiguration(Class<?> testClass) {
- var field = findConfigurationField(testClass);
- if (field == null) {
- return null;
- }
+ static class RavenwoodPropertyState {
- try {
- return (RavenwoodConfig) field.get(null);
- } catch (IllegalAccessException e) {
- throw new RavenwoodRuntimeException("Failed to fetch from the configuration field", e);
- }
- }
+ final List<Pair<String, String>> mBackup;
+ final Set<String> mKeyReadable;
+ final Set<String> mKeyWritable;
- /**
- * @return true if the current target class (or its super classes) has any @Rule / @ClassRule
- * fields of type RavenwoodRule.
- *
- * Note, this check won't detect cases where a Rule is of type
- * {@link TestRule} and still be a {@link RavenwoodRule}. But that'll be detected at runtime
- * as a failure, in {@link #enterRavenwoodRule}.
- */
- private static boolean hasRavenwoodRule(Class<?> testClass) {
- for (var field : testClass.getDeclaredFields()) {
- if (!field.isAnnotationPresent(Rule.class)
- && !field.isAnnotationPresent(ClassRule.class)) {
- continue;
- }
- if (field.getType().equals(RavenwoodRule.class)) {
- return true;
- }
+ RavenwoodPropertyState(RavenwoodTestProperties props) {
+ mBackup = props.mValues.keySet().stream()
+ .map(key -> Pair.create(key, RavenwoodRuntimeNative.getSystemProperty(key)))
+ .toList();
+ mKeyReadable = Set.copyOf(props.mKeyReadable);
+ mKeyWritable = Set.copyOf(props.mKeyWritable);
}
- // JUnit supports rules as methods, so we need to check them too.
- for (var method : testClass.getDeclaredMethods()) {
- if (!method.isAnnotationPresent(Rule.class)
- && !method.isAnnotationPresent(ClassRule.class)) {
- continue;
- }
- if (method.getReturnType().equals(RavenwoodRule.class)) {
- return true;
- }
+
+ boolean isKeyAccessible(String key, boolean write) {
+ return write ? mKeyWritable.contains(key) : mKeyReadable.contains(key);
}
- // Look into the super class.
- if (!testClass.getSuperclass().equals(Object.class)) {
- return hasRavenwoodRule(testClass.getSuperclass());
+
+ void restore() {
+ mBackup.forEach(pair -> {
+ if (pair.second == null) {
+ RavenwoodRuntimeNative.removeSystemProperty(pair.first);
+ } else {
+ RavenwoodRuntimeNative.setSystemProperty(pair.first, pair.second);
+ }
+ });
}
- return false;
}
- /**
- * Find and return a field with @RavenwoodConfig.Config, which must be of type
- * RavenwoodConfig.
- */
- @Nullable
- private static Field findConfigurationField(Class<?> testClass) {
- Field foundField = null;
-
- for (var field : testClass.getDeclaredFields()) {
- final var hasAnot = field.isAnnotationPresent(RavenwoodConfig.Config.class);
- final var isType = field.getType().equals(RavenwoodConfig.class);
+ private static void pushTestProperties(RavenwoodRule rule) {
+ sActiveProperties.add(Pair.create(rule, new RavenwoodPropertyState(rule.mProperties)));
+ rule.mProperties.mValues.forEach(RavenwoodRuntimeNative::setSystemProperty);
+ }
- if (hasAnot) {
- if (isType) {
- // Good, use this field.
- if (foundField != null) {
- fail(String.format(
- "Class %s has multiple fields with %s",
- testClass.getCanonicalName(),
- "@RavenwoodConfig.Config"));
- }
- // Make sure it's static public
- ensureIsPublicMember(field, true);
+ private static void popTestProperties(RavenwoodRule rule) {
+ var pair = sActiveProperties.removeLast();
+ assertNotNull(RAVENWOOD_RULE_ERROR, pair);
+ assertEquals(RAVENWOOD_RULE_ERROR, rule, pair.first);
+ pair.second.restore();
+ }
- foundField = field;
- } else {
- fail(String.format(
- "Field %s.%s has %s but type is not %s",
- testClass.getCanonicalName(),
- field.getName(),
- "@RavenwoodConfig.Config",
- "RavenwoodConfig"));
- return null; // unreachable
- }
- } else {
- if (isType) {
- fail(String.format(
- "Field %s.%s does not have %s but type is %s",
- testClass.getCanonicalName(),
- field.getName(),
- "@RavenwoodConfig.Config",
- "RavenwoodConfig"));
- return null; // unreachable
- } else {
- // Unrelated field, ignore.
- continue;
- }
- }
- }
- if (foundField != null) {
- return foundField;
+ @SuppressWarnings("unused") // Called from native code (ravenwood_sysprop.cpp)
+ private static void checkSystemPropertyAccess(String key, boolean write) {
+ if (write && RavenwoodSystemProperties.sDefaultValues.containsKey(key)) {
+ // The default core values should never be modified
+ throw new IllegalArgumentException(
+ "Setting core system property '" + key + "' is not allowed");
}
- if (!testClass.getSuperclass().equals(Object.class)) {
- return findConfigurationField(testClass.getSuperclass());
+
+ final boolean result = RavenwoodSystemProperties.isKeyAccessible(key, write)
+ || sActiveProperties.stream().anyMatch(p -> p.second.isKeyAccessible(key, write));
+
+ if (!result) {
+ throw new IllegalArgumentException((write ? "Write" : "Read")
+ + " access to system property '" + key + "' denied via RavenwoodRule");
}
- return null;
}
}
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
index 979076eaabfd..e730a292a9da 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
@@ -16,8 +16,11 @@
package android.platform.test.ravenwood;
+import static android.os.Process.FIRST_APPLICATION_UID;
+import static android.os.UserHandle.SYSTEM;
import static android.platform.test.ravenwood.RavenwoodSystemServer.ANDROID_PACKAGE_NAME;
+import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_EMPTY_RESOURCES_APK;
import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_INST_RESOURCE_APK;
import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_RESOURCE_APK;
import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERBOSE_LOGGING;
@@ -25,7 +28,9 @@ import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERSIO
import static com.android.ravenwood.common.RavenwoodCommonUtils.parseNullableInt;
import static com.android.ravenwood.common.RavenwoodCommonUtils.withDefault;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
@@ -53,6 +58,7 @@ import android.provider.DeviceConfig_host;
import android.system.ErrnoException;
import android.system.Os;
import android.util.Log;
+import android.view.DisplayAdjustments;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -62,7 +68,6 @@ import com.android.internal.os.RuntimeInit;
import com.android.ravenwood.RavenwoodRuntimeNative;
import com.android.ravenwood.RavenwoodRuntimeState;
import com.android.ravenwood.common.RavenwoodCommonUtils;
-import com.android.ravenwood.common.RavenwoodRuntimeException;
import com.android.ravenwood.common.SneakyThrow;
import com.android.server.LocalServices;
import com.android.server.compat.PlatformCompat;
@@ -74,8 +79,10 @@ import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.util.Collections;
+import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
+import java.util.Random;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
@@ -85,8 +92,7 @@ import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
/**
- * Responsible for initializing and de-initializing the environment, according to a
- * {@link RavenwoodConfig}.
+ * Responsible for initializing and the environment.
*/
public class RavenwoodRuntimeEnvironmentController {
private static final String TAG = "RavenwoodRuntimeEnvironmentController";
@@ -113,8 +119,6 @@ public class RavenwoodRuntimeEnvironmentController {
private static ScheduledFuture<?> sPendingTimeout;
- private static long sOriginalIdentityToken = -1;
-
/**
* When enabled, attempt to detect uncaught exceptions from background threads.
*/
@@ -147,6 +151,10 @@ public class RavenwoodRuntimeEnvironmentController {
return res;
}
+ /** Map from path -> resources. */
+ private static final HashMap<File, Resources> sCachedResources = new HashMap<>();
+ private static Set<String> sAdoptedPermissions = Collections.emptySet();
+
private static final Object sInitializationLock = new Object();
@GuardedBy("sInitializationLock")
@@ -155,15 +163,16 @@ public class RavenwoodRuntimeEnvironmentController {
@GuardedBy("sInitializationLock")
private static Throwable sExceptionFromGlobalInit;
- private static RavenwoodAwareTestRunner sRunner;
- private static RavenwoodSystemProperties sProps;
-
private static final int DEFAULT_TARGET_SDK_LEVEL = VERSION_CODES.CUR_DEVELOPMENT;
private static final String DEFAULT_PACKAGE_NAME = "com.android.ravenwoodtests.defaultname";
+ private static final int sMyPid = new Random().nextInt(100, 32768);
private static int sTargetSdkLevel;
private static String sTestPackageName;
private static String sTargetPackageName;
+ private static Instrumentation sInstrumentation;
+ private static final long sCallingIdentity =
+ packBinderIdentityToken(false, FIRST_APPLICATION_UID, sMyPid);
/**
* Initialize the global environment.
@@ -182,7 +191,7 @@ public class RavenwoodRuntimeEnvironmentController {
Log.e(TAG, "globalInit() failed", th);
sExceptionFromGlobalInit = th;
- throw th;
+ SneakyThrow.sneakyThrow(th);
}
} else {
// Subsequent calls. If the first call threw, just throw the same error, to prevent
@@ -197,10 +206,13 @@ public class RavenwoodRuntimeEnvironmentController {
}
}
- private static void globalInitInner() {
+ private static void globalInitInner() throws IOException {
if (RAVENWOOD_VERBOSE_LOGGING) {
Log.v(TAG, "globalInit() called here...", new RuntimeException("NOT A CRASH"));
}
+ if (ENABLE_UNCAUGHT_EXCEPTION_DETECTION) {
+ Thread.setDefaultUncaughtExceptionHandler(sUncaughtExceptionHandler);
+ }
// Some process-wide initialization. (maybe redirect stdout/stderr)
RavenwoodCommonUtils.loadJniLibrary(LIBRAVENWOOD_INITIALIZER_NAME);
@@ -220,7 +232,6 @@ public class RavenwoodRuntimeEnvironmentController {
// Do the basic set up for the android sysprops.
RavenwoodSystemProperties.initialize();
- setSystemProperties(null);
// Do this after loading RAVENWOOD_NATIVE_RUNTIME_NAME (which backs Os.setenv()),
// before loadFrameworkNativeCode() (which uses $ANDROID_LOG_TAGS).
@@ -251,82 +262,32 @@ public class RavenwoodRuntimeEnvironmentController {
loadRavenwoodProperties();
assertMockitoVersion();
- }
-
- private static void loadRavenwoodProperties() {
- var props = RavenwoodSystemProperties.readProperties("ravenwood.properties");
-
- sTargetSdkLevel = withDefault(
- parseNullableInt(props.get("targetSdkVersionInt")), DEFAULT_TARGET_SDK_LEVEL);
- sTargetPackageName = withDefault(props.get("packageName"), DEFAULT_PACKAGE_NAME);
- sTestPackageName = withDefault(props.get("instPackageName"), sTargetPackageName);
-
- // TODO(b/377765941) Read them from the manifest too?
- }
-
- /**
- * Initialize the environment.
- */
- public static void init(RavenwoodAwareTestRunner runner) {
- if (RAVENWOOD_VERBOSE_LOGGING) {
- Log.v(TAG, "init() called here: " + runner, new RuntimeException("STACKTRACE"));
- }
- if (sRunner == runner) {
- return;
- }
- if (sRunner != null) {
- reset();
- }
- sRunner = runner;
- try {
- initInner(runner.mState.getConfig());
- } catch (Exception th) {
- Log.e(TAG, "init() failed", th);
-
- RavenwoodCommonUtils.runIgnoringException(()-> reset());
-
- SneakyThrow.sneakyThrow(th);
- }
- }
-
- private static void initInner(RavenwoodConfig config) throws IOException {
- if (ENABLE_UNCAUGHT_EXCEPTION_DETECTION) {
- maybeThrowPendingUncaughtException(false);
- Thread.setDefaultUncaughtExceptionHandler(sUncaughtExceptionHandler);
- }
-
- config.mTargetPackageName = sTargetPackageName;
- config.mTestPackageName = sTestPackageName;
- config.mTargetSdkLevel = sTargetSdkLevel;
Log.i(TAG, "TargetPackageName=" + sTargetPackageName);
Log.i(TAG, "TestPackageName=" + sTestPackageName);
Log.i(TAG, "TargetSdkLevel=" + sTargetSdkLevel);
- RavenwoodRuntimeState.sUid = config.mUid;
- RavenwoodRuntimeState.sPid = config.mPid;
- RavenwoodRuntimeState.sTargetSdkLevel = config.mTargetSdkLevel;
- sOriginalIdentityToken = Binder.clearCallingIdentity();
- reinit();
- setSystemProperties(config.mSystemProperties);
+ RavenwoodRuntimeState.sUid = FIRST_APPLICATION_UID;
+ RavenwoodRuntimeState.sPid = sMyPid;
+ RavenwoodRuntimeState.sTargetSdkLevel = sTargetSdkLevel;
ServiceManager.init$ravenwood();
LocalServices.removeAllServicesForTest();
- ActivityManager.init$ravenwood(config.mCurrentUser);
+ ActivityManager.init$ravenwood(SYSTEM.getIdentifier());
final var main = new HandlerThread(MAIN_THREAD_NAME);
main.start();
Looper.setMainLooperForTest(main.getLooper());
final boolean isSelfInstrumenting =
- Objects.equals(config.mTestPackageName, config.mTargetPackageName);
+ Objects.equals(sTestPackageName, sTargetPackageName);
// This will load the resources from the apk set to `resource_apk` in the build file.
// This is supposed to be the "target app"'s resources.
final Supplier<Resources> targetResourcesLoader = () -> {
var file = new File(RAVENWOOD_RESOURCE_APK);
- return config.mState.loadResources(file.exists() ? file : null);
+ return loadResources(file.exists() ? file : null);
};
// Set up test context's (== instrumentation context's) resources.
@@ -337,18 +298,17 @@ public class RavenwoodRuntimeEnvironmentController {
} else {
instResourcesLoader = () -> {
var file = new File(RAVENWOOD_INST_RESOURCE_APK);
- return config.mState.loadResources(file.exists() ? file : null);
+ return loadResources(file.exists() ? file : null);
};
}
var instContext = new RavenwoodContext(
- config.mTestPackageName, main, instResourcesLoader);
+ sTestPackageName, main, instResourcesLoader);
var targetContext = new RavenwoodContext(
- config.mTargetPackageName, main, targetResourcesLoader);
+ sTargetPackageName, main, targetResourcesLoader);
// Set up app context.
- var appContext = new RavenwoodContext(
- config.mTargetPackageName, main, targetResourcesLoader);
+ var appContext = new RavenwoodContext(sTargetPackageName, main, targetResourcesLoader);
appContext.setApplicationContext(appContext);
if (isSelfInstrumenting) {
instContext.setApplicationContext(appContext);
@@ -357,42 +317,68 @@ public class RavenwoodRuntimeEnvironmentController {
// When instrumenting into another APK, the test context doesn't have an app context.
targetContext.setApplicationContext(appContext);
}
- config.mInstContext = instContext;
- config.mTargetContext = targetContext;
- final Supplier<Resources> systemResourcesLoader = () -> config.mState.loadResources(null);
+ final Supplier<Resources> systemResourcesLoader = () -> loadResources(null);
- config.mState.mSystemServerContext =
+ var systemServerContext =
new RavenwoodContext(ANDROID_PACKAGE_NAME, main, systemResourcesLoader);
- // Prepare other fields.
- config.mInstrumentation = new Instrumentation();
- config.mInstrumentation.basicInit(instContext, targetContext, createMockUiAutomation());
- InstrumentationRegistry.registerInstance(config.mInstrumentation, Bundle.EMPTY);
+ sInstrumentation = new Instrumentation();
+ sInstrumentation.basicInit(instContext, targetContext, null);
+ InstrumentationRegistry.registerInstance(sInstrumentation, Bundle.EMPTY);
+
+ RavenwoodSystemServer.init(systemServerContext);
+
+ initializeCompatIds();
+ }
+
+ private static void loadRavenwoodProperties() {
+ var props = RavenwoodSystemProperties.readProperties("ravenwood.properties");
+
+ sTargetSdkLevel = withDefault(
+ parseNullableInt(props.get("targetSdkVersionInt")), DEFAULT_TARGET_SDK_LEVEL);
+ sTargetPackageName = withDefault(props.get("packageName"), DEFAULT_PACKAGE_NAME);
+ sTestPackageName = withDefault(props.get("instPackageName"), sTargetPackageName);
+
+ // TODO(b/377765941) Read them from the manifest too?
+ }
- RavenwoodSystemServer.init(config);
+ /**
+ * Partially reset and initialize before each test class invocation
+ */
+ public static void initForRunner() {
+ var targetContext = sInstrumentation.getTargetContext();
+ var instContext = sInstrumentation.getContext();
+ // We need to recreate the mock UiAutomation for each test class, because sometimes tests
+ // will call Mockito.framework().clearInlineMocks() after execution.
+ sInstrumentation.basicInit(instContext, targetContext, createMockUiAutomation());
+
+ // Reset some global state
+ Process_ravenwood.reset();
+ DeviceConfig_host.reset();
+ Binder.restoreCallingIdentity(sCallingIdentity);
- initializeCompatIds(config);
+ SystemProperties.clearChangeCallbacksForTest();
if (ENABLE_TIMEOUT_STACKS) {
sPendingTimeout = sTimeoutExecutor.schedule(
RavenwoodRuntimeEnvironmentController::dumpStacks,
TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
}
+ if (ENABLE_UNCAUGHT_EXCEPTION_DETECTION) {
+ maybeThrowPendingUncaughtException(false);
+ }
}
/**
- * Partially re-initialize after each test method invocation
+ * Partially reset and initialize before each test method invocation
*/
- public static void reinit() {
- // sRunner could be null, if there was a failure in the initialization.
- if (sRunner != null) {
- var config = sRunner.mState.getConfig();
- Binder.restoreCallingIdentity(packBinderIdentityToken(false, config.mUid, config.mPid));
- }
+ public static void initForMethod() {
+ // TODO(b/375272444): this is a hacky workaround to ensure binder identity
+ Binder.restoreCallingIdentity(sCallingIdentity);
}
- private static void initializeCompatIds(RavenwoodConfig config) {
+ private static void initializeCompatIds() {
// Set up compat-IDs for the app side.
// TODO: Inside the system server, all the compat-IDs should be enabled,
// Due to the `AppCompatCallbacks.install(new long[0], new long[0])` call in
@@ -400,8 +386,8 @@ public class RavenwoodRuntimeEnvironmentController {
// Compat framework only uses the package name and the target SDK level.
ApplicationInfo appInfo = new ApplicationInfo();
- appInfo.packageName = config.mTargetPackageName;
- appInfo.targetSdkVersion = config.mTargetSdkLevel;
+ appInfo.packageName = sTargetPackageName;
+ appInfo.targetSdkVersion = sTargetSdkLevel;
PlatformCompat platformCompat = null;
try {
@@ -418,65 +404,42 @@ public class RavenwoodRuntimeEnvironmentController {
}
/**
- * De-initialize.
- *
- * Note, we call this method when init() fails too, so this method should deal with
- * any partially-initialized states.
+ * Load {@link Resources} from an APK, with cache.
*/
- public static void reset() {
- if (RAVENWOOD_VERBOSE_LOGGING) {
- Log.v(TAG, "reset() called here", new RuntimeException("STACKTRACE"));
- }
- if (sRunner == null) {
- throw new RavenwoodRuntimeException("Internal error: reset() already called");
+ private static Resources loadResources(@Nullable File apkPath) {
+ var cached = sCachedResources.get(apkPath);
+ if (cached != null) {
+ return cached;
}
- var config = sRunner.mState.getConfig();
- sRunner = null;
- if (ENABLE_TIMEOUT_STACKS) {
- sPendingTimeout.cancel(false);
- }
+ var fileToLoad = apkPath != null ? apkPath : new File(RAVENWOOD_EMPTY_RESOURCES_APK);
- RavenwoodSystemServer.reset(config);
+ assertTrue("File " + fileToLoad + " doesn't exist.", fileToLoad.isFile());
- InstrumentationRegistry.registerInstance(null, Bundle.EMPTY);
- config.mInstrumentation = null;
- if (config.mInstContext != null) {
- ((RavenwoodContext) config.mInstContext).cleanUp();
- config.mInstContext = null;
- }
- if (config.mTargetContext != null) {
- ((RavenwoodContext) config.mTargetContext).cleanUp();
- config.mTargetContext = null;
- }
- if (config.mState.mSystemServerContext != null) {
- config.mState.mSystemServerContext.cleanUp();
- }
+ final String path = fileToLoad.getAbsolutePath();
+ final var emptyPaths = new String[0];
- if (Looper.getMainLooper() != null) {
- Looper.getMainLooper().quit();
- }
- Looper.clearMainLooperForTest();
+ ResourcesManager.getInstance().initializeApplicationPaths(path, emptyPaths);
- ActivityManager.reset$ravenwood();
+ final var ret = ResourcesManager.getInstance().getResources(null, path,
+ emptyPaths, emptyPaths, emptyPaths,
+ emptyPaths, null, null,
+ new DisplayAdjustments().getCompatibilityInfo(),
+ RavenwoodRuntimeEnvironmentController.class.getClassLoader(), null);
- LocalServices.removeAllServicesForTest();
- ServiceManager.reset$ravenwood();
+ assertNotNull(ret);
- setSystemProperties(null);
- if (sOriginalIdentityToken != -1) {
- Binder.restoreCallingIdentity(sOriginalIdentityToken);
- }
- RavenwoodRuntimeState.reset();
- Process_ravenwood.reset();
- DeviceConfig_host.reset();
+ sCachedResources.put(apkPath, ret);
+ return ret;
+ }
- try {
- ResourcesManager.setInstance(null); // Better structure needed.
- } catch (Exception e) {
- // AOSP-CHANGE: AOSP doesn't support resources yet.
+ /**
+ * A callback when a test class finishes its execution, mostly only for debugging.
+ */
+ public static void exitTestClass() {
+ if (ENABLE_TIMEOUT_STACKS) {
+ sPendingTimeout.cancel(false);
}
-
if (ENABLE_UNCAUGHT_EXCEPTION_DETECTION) {
maybeThrowPendingUncaughtException(true);
}
@@ -521,19 +484,6 @@ public class RavenwoodRuntimeEnvironmentController {
}
}
- /**
- * Set the current configuration to the actual SystemProperties.
- */
- private static void setSystemProperties(@Nullable RavenwoodSystemProperties systemProperties) {
- SystemProperties.clearChangeCallbacksForTest();
- RavenwoodRuntimeNative.clearSystemProperties();
- if (systemProperties == null) systemProperties = new RavenwoodSystemProperties();
- sProps = new RavenwoodSystemProperties(systemProperties, true);
- for (var entry : systemProperties.getValues().entrySet()) {
- RavenwoodRuntimeNative.setSystemProperty(entry.getKey(), entry.getValue());
- }
- }
-
private static final String MOCKITO_ERROR = "FATAL: Unsupported Mockito detected!"
+ " Your test or its dependencies use one of the \"mockito-target-*\""
+ " modules as static library, which is unusable on host side."
@@ -558,40 +508,31 @@ public class RavenwoodRuntimeEnvironmentController {
// TODO: use the real UiAutomation class instead of a mock
private static UiAutomation createMockUiAutomation() {
- final Set[] adoptedPermission = { Collections.emptySet() };
+ sAdoptedPermissions = Collections.emptySet();
var mock = mock(UiAutomation.class, inv -> {
HostTestUtils.onThrowMethodCalled();
return null;
});
doAnswer(inv -> {
- adoptedPermission[0] = UiAutomation.ALL_PERMISSIONS;
+ sAdoptedPermissions = UiAutomation.ALL_PERMISSIONS;
return null;
}).when(mock).adoptShellPermissionIdentity();
doAnswer(inv -> {
if (inv.getArgument(0) == null) {
- adoptedPermission[0] = UiAutomation.ALL_PERMISSIONS;
+ sAdoptedPermissions = UiAutomation.ALL_PERMISSIONS;
} else {
- adoptedPermission[0] = Set.of(inv.getArguments());
+ sAdoptedPermissions = (Set) Set.of(inv.getArguments());
}
return null;
}).when(mock).adoptShellPermissionIdentity(any());
doAnswer(inv -> {
- adoptedPermission[0] = Collections.emptySet();
+ sAdoptedPermissions = Collections.emptySet();
return null;
}).when(mock).dropShellPermissionIdentity();
- doAnswer(inv -> adoptedPermission[0]).when(mock).getAdoptedShellPermissions();
+ doAnswer(inv -> sAdoptedPermissions).when(mock).getAdoptedShellPermissions();
return mock;
}
- @SuppressWarnings("unused") // Called from native code (ravenwood_sysprop.cpp)
- private static void checkSystemPropertyAccess(String key, boolean write) {
- boolean result = write ? sProps.isKeyWritable(key) : sProps.isKeyReadable(key);
- if (!result) {
- throw new IllegalArgumentException((write ? "Write" : "Read")
- + " access to system property '" + key + "' denied via RavenwoodConfig");
- }
- }
-
private static void dumpCommandLineArgs() {
Log.i(TAG, "JVM arguments:");
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java
index 9bd376a76f77..c545baacdf3e 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package android.platform.test.ravenwood;
import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERBOSE_LOGGING;
@@ -21,26 +20,30 @@ import static com.android.ravenwood.common.RavenwoodCommonUtils.getRavenwoodRunt
import android.util.Log;
+import com.android.ravenwood.RavenwoodRuntimeNative;
+
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
+/**
+ * A class to manage the core default system properties of the Ravenwood environment.
+ */
public class RavenwoodSystemProperties {
private static final String TAG = "RavenwoodSystemProperties";
- /** We pull in propeties from this file. */
+ /** We pull in properties from this file. */
private static final String RAVENWOOD_BUILD_PROP = "ravenwood-data/ravenwood-build.prop";
/** This is the actual build.prop we use to build the device (contents depends on lunch). */
private static final String DEVICE_BUILD_PROP = "ravenwood-data/build.prop";
/** The default values. */
- private static final Map<String, String> sDefaultValues = new HashMap<>();
+ static final Map<String, String> sDefaultValues = new HashMap<>();
private static final String[] PARTITIONS = {
"bootimage",
@@ -91,7 +94,7 @@ public class RavenwoodSystemProperties {
var deviceValue = deviceProps.get(deviceKey);
if (deviceValue == null) {
throw new RuntimeException("Failed to initialize system properties. Key '"
- + deviceKey + "' doesn't exist in the device side build.prop");
+ + deviceKey + "' doesn't exist in the device side build.prop");
}
value = deviceValue;
} else {
@@ -115,6 +118,7 @@ public class RavenwoodSystemProperties {
}
}
}
+
if (RAVENWOOD_VERBOSE_LOGGING) {
// Dump all properties for local debugging.
Log.v(TAG, "All system properties:");
@@ -122,35 +126,12 @@ public class RavenwoodSystemProperties {
Log.v(TAG, "" + key + "=" + sDefaultValues.get(key));
}
}
- }
-
- private volatile boolean mIsImmutable;
-
- private final Map<String, String> mValues = new HashMap<>();
-
- /** Set of additional keys that should be considered readable */
- private final Set<String> mKeyReadable = new HashSet<>();
-
- /** Set of additional keys that should be considered writable */
- private final Set<String> mKeyWritable = new HashSet<>();
-
- public RavenwoodSystemProperties() {
- mValues.putAll(sDefaultValues);
- }
-
- /** Copy constructor */
- public RavenwoodSystemProperties(RavenwoodSystemProperties source, boolean immutable) {
- mKeyReadable.addAll(source.mKeyReadable);
- mKeyWritable.addAll(source.mKeyWritable);
- mValues.putAll(source.mValues);
- mIsImmutable = immutable;
- }
- public Map<String, String> getValues() {
- return new HashMap<>(mValues);
+ // Actually set the system properties
+ sDefaultValues.forEach(RavenwoodRuntimeNative::setSystemProperty);
}
- public boolean isKeyReadable(String key) {
+ private static boolean isKeyReadable(String key) {
final String root = getKeyRoot(key);
if (root.startsWith("debug.")) return true;
@@ -183,10 +164,10 @@ public class RavenwoodSystemProperties {
return true;
}
- return mKeyReadable.contains(key);
+ return false;
}
- public boolean isKeyWritable(String key) {
+ private static boolean isKeyWritable(String key) {
final String root = getKeyRoot(key);
if (root.startsWith("debug.")) return true;
@@ -194,42 +175,11 @@ public class RavenwoodSystemProperties {
// For PropertyInvalidatedCache
if (root.startsWith("cache_key.")) return true;
- return mKeyWritable.contains(key);
- }
-
- private void ensureNotImmutable() {
- if (mIsImmutable) {
- throw new RuntimeException("Unable to update immutable instance");
- }
- }
-
- public void setValue(String key, Object value) {
- ensureNotImmutable();
-
- final String valueString = (value == null) ? null : String.valueOf(value);
- if ((valueString == null) || valueString.isEmpty()) {
- mValues.remove(key);
- } else {
- mValues.put(key, valueString);
- }
- }
-
- public void setAccessNone(String key) {
- ensureNotImmutable();
- mKeyReadable.remove(key);
- mKeyWritable.remove(key);
- }
-
- public void setAccessReadOnly(String key) {
- ensureNotImmutable();
- mKeyReadable.add(key);
- mKeyWritable.remove(key);
+ return false;
}
- public void setAccessReadWrite(String key) {
- ensureNotImmutable();
- mKeyReadable.add(key);
- mKeyWritable.add(key);
+ static boolean isKeyAccessible(String key, boolean write) {
+ return write ? isKeyWritable(key) : isKeyReadable(key);
}
/**
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java
index 438a2bfa7a14..3346635b7f7b 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodSystemServer.java
@@ -33,7 +33,7 @@ import com.android.server.compat.PlatformCompat;
import com.android.server.compat.PlatformCompatNative;
import com.android.server.utils.TimingsTraceAndSlog;
-import java.util.List;
+import java.util.Collection;
import java.util.Set;
public class RavenwoodSystemServer {
@@ -68,27 +68,24 @@ public class RavenwoodSystemServer {
private static TimingsTraceAndSlog sTimings;
private static SystemServiceManager sServiceManager;
- public static void init(RavenwoodConfig config) {
+ public static void init(Context systemServerContext) {
// Always start PlatformCompat, regardless of the requested services.
// PlatformCompat is not really a SystemService, so it won't receive boot phases / etc.
// This initialization code is copied from SystemServer.java.
- PlatformCompat platformCompat = new PlatformCompat(config.mState.mSystemServerContext);
+ PlatformCompat platformCompat = new PlatformCompat(systemServerContext);
ServiceManager.addService(Context.PLATFORM_COMPAT_SERVICE, platformCompat);
ServiceManager.addService(Context.PLATFORM_COMPAT_NATIVE_SERVICE,
new PlatformCompatNative(platformCompat));
- // Avoid overhead if no services required
- if (config.mServicesRequired.isEmpty()) return;
-
sStartedServices = new ArraySet<>();
sTimings = new TimingsTraceAndSlog();
- sServiceManager = new SystemServiceManager(config.mState.mSystemServerContext);
+ sServiceManager = new SystemServiceManager(systemServerContext);
sServiceManager.setStartInfo(false,
SystemClock.elapsedRealtime(),
SystemClock.uptimeMillis());
LocalServices.addService(SystemServiceManager.class, sServiceManager);
- startServices(config.mServicesRequired);
+ startServices(sKnownServices.keySet());
sServiceManager.sealStartedServices();
// TODO: expand to include additional boot phases when relevant
@@ -96,7 +93,7 @@ public class RavenwoodSystemServer {
sServiceManager.startBootPhase(sTimings, SystemService.PHASE_BOOT_COMPLETED);
}
- public static void reset(RavenwoodConfig config) {
+ public static void reset() {
// TODO: consider introducing shutdown boot phases
LocalServices.removeServiceForTest(SystemServiceManager.class);
@@ -105,7 +102,7 @@ public class RavenwoodSystemServer {
sStartedServices = null;
}
- private static void startServices(List<Class<?>> serviceClasses) {
+ private static void startServices(Collection<Class<?>> serviceClasses) {
for (Class<?> serviceClass : serviceClasses) {
// Quietly ignore duplicate requests if service already started
if (sStartedServices.contains(serviceClass)) continue;
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java
index 7ca9239d2062..3ed0f50434fb 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java
@@ -15,21 +15,13 @@
*/
package android.platform.test.ravenwood;
-import static android.os.Process.FIRST_APPLICATION_UID;
-import static android.os.UserHandle.SYSTEM;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.Instrumentation;
-import android.content.Context;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicInteger;
/**
* @deprecated This class will be removed. Reach out to g/ravenwood if you need any features in it.
@@ -45,37 +37,10 @@ public final class RavenwoodConfig {
public @interface Config {
}
- private static final int NOBODY_UID = 9999;
-
- private static final AtomicInteger sNextPid = new AtomicInteger(100);
-
- int mCurrentUser = SYSTEM.getIdentifier();
-
- /**
- * Unless the test author requests differently, run as "nobody", and give each collection of
- * tests its own unique PID.
- */
- int mUid = FIRST_APPLICATION_UID;
- int mPid = sNextPid.getAndIncrement();
-
- String mTestPackageName;
- String mTargetPackageName;
-
- int mTargetSdkLevel;
-
- final RavenwoodSystemProperties mSystemProperties = new RavenwoodSystemProperties();
-
- final List<Class<?>> mServicesRequired = new ArrayList<>();
-
- volatile Context mInstContext;
- volatile Context mTargetContext;
- volatile Instrumentation mInstrumentation;
-
/**
* Stores internal states / methods associated with this config that's only needed in
* junit-impl.
*/
- final RavenwoodConfigState mState = new RavenwoodConfigState(this);
private RavenwoodConfig() {
}
@@ -159,34 +124,11 @@ public final class RavenwoodConfig {
return this;
}
- Builder setSystemPropertyImmutableReal(@NonNull String key,
- @Nullable Object value) {
- mConfig.mSystemProperties.setValue(key, value);
- mConfig.mSystemProperties.setAccessReadOnly(key);
- return this;
- }
-
- Builder setSystemPropertyMutableReal(@NonNull String key,
- @Nullable Object value) {
- mConfig.mSystemProperties.setValue(key, value);
- mConfig.mSystemProperties.setAccessReadWrite(key);
- return this;
- }
-
/**
- * Configure the set of system services that are required for this test to operate.
- *
- * For example, passing {@code android.hardware.SerialManager.class} as an argument will
- * ensure that the underlying service is created, initialized, and ready to use for the
- * duration of the test. The {@code SerialManager} instance can be obtained via
- * {@code RavenwoodRule.getContext()} and {@code Context.getSystemService()}, and
- * {@code SerialManagerInternal} can be obtained via {@code LocalServices.getService()}.
+ * @deprecated no longer used. All supported services are available.
*/
+ @Deprecated
public Builder setServicesRequired(@NonNull Class<?>... services) {
- mConfig.mServicesRequired.clear();
- for (Class<?> service : services) {
- mConfig.mServicesRequired.add(service);
- }
return this;
}
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index 5681a9040f63..e49d3d934e9f 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -92,19 +92,11 @@ public final class RavenwoodRule implements TestRule {
}
}
- private final RavenwoodConfig mConfiguration;
-
- public RavenwoodRule() {
- mConfiguration = new RavenwoodConfig.Builder().build();
- }
-
- private RavenwoodRule(RavenwoodConfig config) {
- mConfiguration = config;
- }
+ final RavenwoodTestProperties mProperties = new RavenwoodTestProperties();
public static class Builder {
- private final RavenwoodConfig.Builder mBuilder =
- new RavenwoodConfig.Builder();
+
+ private final RavenwoodRule mRule = new RavenwoodRule();
public Builder() {
}
@@ -152,7 +144,8 @@ public final class RavenwoodRule implements TestRule {
* Has no effect on non-Ravenwood environments.
*/
public Builder setSystemPropertyImmutable(@NonNull String key, @Nullable Object value) {
- mBuilder.setSystemPropertyImmutableReal(key, value);
+ mRule.mProperties.setValue(key, value);
+ mRule.mProperties.setAccessReadOnly(key);
return this;
}
@@ -167,26 +160,21 @@ public final class RavenwoodRule implements TestRule {
* Has no effect on non-Ravenwood environments.
*/
public Builder setSystemPropertyMutable(@NonNull String key, @Nullable Object value) {
- mBuilder.setSystemPropertyMutableReal(key, value);
+ mRule.mProperties.setValue(key, value);
+ mRule.mProperties.setAccessReadWrite(key);
return this;
}
/**
- * Configure the set of system services that are required for this test to operate.
- *
- * For example, passing {@code android.hardware.SerialManager.class} as an argument will
- * ensure that the underlying service is created, initialized, and ready to use for the
- * duration of the test. The {@code SerialManager} instance can be obtained via
- * {@code RavenwoodRule.getContext()} and {@code Context.getSystemService()}, and
- * {@code SerialManagerInternal} can be obtained via {@code LocalServices.getService()}.
+ * @deprecated no longer used. All supported services are available.
*/
+ @Deprecated
public Builder setServicesRequired(@NonNull Class<?>... services) {
- mBuilder.setServicesRequired(services);
return this;
}
public RavenwoodRule build() {
- return new RavenwoodRule(mBuilder.build());
+ return mRule;
}
}
@@ -227,7 +215,7 @@ public final class RavenwoodRule implements TestRule {
@Override
public Statement apply(Statement base, Description description) {
- if (!RavenwoodConfig.isOnRavenwood()) {
+ if (!IS_ON_RAVENWOOD) {
return base;
}
return new Statement() {
@@ -296,8 +284,4 @@ public final class RavenwoodRule implements TestRule {
public static RavenwoodPrivate private$ravenwood() {
return sRavenwoodPrivate;
}
-
- RavenwoodConfig getConfiguration() {
- return mConfiguration;
- }
}
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodTestProperties.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodTestProperties.java
new file mode 100644
index 000000000000..66a26b511213
--- /dev/null
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodTestProperties.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.platform.test.ravenwood;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A class to store system properties defined by tests.
+ */
+public class RavenwoodTestProperties {
+ final Map<String, String> mValues = new HashMap<>();
+
+ /** Set of additional keys that should be considered readable */
+ final Set<String> mKeyReadable = new HashSet<>();
+
+ /** Set of additional keys that should be considered writable */
+ final Set<String> mKeyWritable = new HashSet<>();
+
+ public void setValue(String key, Object value) {
+ final String valueString = (value == null) ? null : String.valueOf(value);
+ if ((valueString == null) || valueString.isEmpty()) {
+ mValues.remove(key);
+ } else {
+ mValues.put(key, valueString);
+ }
+ }
+
+ public void setAccessNone(String key) {
+ mKeyReadable.remove(key);
+ mKeyWritable.remove(key);
+ }
+
+ public void setAccessReadOnly(String key) {
+ mKeyReadable.add(key);
+ mKeyWritable.remove(key);
+ }
+
+ public void setAccessReadWrite(String key) {
+ mKeyReadable.add(key);
+ mKeyWritable.add(key);
+ }
+}
diff --git a/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java
index 7b940b423b69..9a78989dad55 100644
--- a/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java
+++ b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java
@@ -56,7 +56,11 @@ public class RavenwoodRuntimeNative {
public static native boolean setSystemProperty(String key, String value);
- public static native void clearSystemProperties();
+ public static native boolean removeSystemProperty(String key);
+
+ public static void clearSystemProperties() {
+ removeSystemProperty(null);
+ }
public static native int gettid();
diff --git a/ravenwood/runtime-jni/jni_helper.h b/ravenwood/runtime-jni/jni_helper.h
index 561fb3beda6b..25d75193de09 100644
--- a/ravenwood/runtime-jni/jni_helper.h
+++ b/ravenwood/runtime-jni/jni_helper.h
@@ -26,6 +26,7 @@
constexpr const char* kCommonUtils = "com/android/ravenwood/common/RavenwoodCommonUtils";
constexpr const char* kRuntimeEnvController =
"android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController";
+constexpr const char* kRunnerState = "android/platform/test/ravenwood/RavenwoodRunnerState";
constexpr const char* kRuntimeNative = "com/android/ravenwood/RavenwoodRuntimeNative";
// We have to explicitly decode the string to real UTF-8, because when using GetStringUTFChars
diff --git a/ravenwood/runtime-jni/ravenwood_sysprop.cpp b/ravenwood/runtime-jni/ravenwood_sysprop.cpp
index aafc4268d782..a78aa8da9052 100644
--- a/ravenwood/runtime-jni/ravenwood_sysprop.cpp
+++ b/ravenwood/runtime-jni/ravenwood_sysprop.cpp
@@ -117,7 +117,7 @@ void __system_property_read_callback(const prop_info* pi,
// ---- JNI ----
static JavaVM* gVM = nullptr;
-static jclass gEnvController = nullptr;
+static jclass gRunnerState = nullptr;
static jmethodID gCheckSystemPropertyAccess;
static void reloadNativeLibrary(JNIEnv* env, jclass, jstring javaPath) {
@@ -128,11 +128,11 @@ static void reloadNativeLibrary(JNIEnv* env, jclass, jstring javaPath) {
// Call back into Java code to check property access
static void check_system_property_access(const char* key, bool write) {
- if (gVM != nullptr && gEnvController != nullptr) {
+ if (gVM != nullptr && gRunnerState != nullptr) {
JNIEnv* env;
if (gVM->GetEnv((void**)&env, JNI_VERSION_1_4) >= 0) {
ALOGI("%s access to system property '%s'", write ? "Write" : "Read", key);
- env->CallStaticVoidMethod(gEnvController, gCheckSystemPropertyAccess,
+ env->CallStaticVoidMethod(gRunnerState, gCheckSystemPropertyAccess,
env->NewStringUTF(key), write ? JNI_TRUE : JNI_FALSE);
return;
}
@@ -155,16 +155,29 @@ static jboolean setSystemProperty(JNIEnv* env, jclass, jstring javaKey, jstring
return property_set(key.c_str(), value.c_str()) ? JNI_TRUE : JNI_FALSE;
}
-static void clearSystemProperties(JNIEnv*, jclass) {
+static jboolean removeSystemProperty(JNIEnv* env, jclass, jstring javaKey) {
std::lock_guard lock(g_properties_lock);
- g_properties.clear();
+
+ if (javaKey == nullptr) {
+ g_properties.clear();
+ return JNI_TRUE;
+ } else {
+ ScopedUtfChars key(env, javaKey);
+ auto it = g_properties.find(key);
+ if (it != g_properties.end()) {
+ g_properties.erase(it);
+ return JNI_TRUE;
+ } else {
+ return JNI_FALSE;
+ }
+ }
}
static const JNINativeMethod sMethods[] = {
{"reloadNativeLibrary", "(Ljava/lang/String;)V", (void*)reloadNativeLibrary},
{"getSystemProperty", "(Ljava/lang/String;)Ljava/lang/String;", (void*)getSystemProperty},
{"setSystemProperty", "(Ljava/lang/String;Ljava/lang/String;)Z", (void*)setSystemProperty},
- {"clearSystemProperties", "()V", (void*)clearSystemProperties},
+ {"removeSystemProperty", "(Ljava/lang/String;)Z", (void*)removeSystemProperty},
};
extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) {
@@ -174,9 +187,9 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) {
gVM = vm;
// Fetch several references for future use
- gEnvController = FindGlobalClassOrDie(env, kRuntimeEnvController);
+ gRunnerState = FindGlobalClassOrDie(env, kRunnerState);
gCheckSystemPropertyAccess =
- GetStaticMethodIDOrDie(env, gEnvController, "checkSystemPropertyAccess",
+ GetStaticMethodIDOrDie(env, gRunnerState, "checkSystemPropertyAccess",
"(Ljava/lang/String;Z)V");
// Expose raw property methods as JNI methods
diff --git a/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodMultipleRuleTest.java b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodMultipleRuleTest.java
deleted file mode 100644
index c25d2b4cbc4d..000000000000
--- a/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/RavenwoodMultipleRuleTest.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.ravenwoodtest.bivalenttest;
-
-import android.platform.test.ravenwood.RavenwoodConfig;
-import android.platform.test.ravenwood.RavenwoodRule;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import org.junit.Assume;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.junit.runner.RunWith;
-
-/**
- * Make sure having multiple RavenwoodRule's is detected.
- * (But only when running on ravenwod. Otherwise it'll be ignored.)
- */
-@RunWith(AndroidJUnit4.class)
-public class RavenwoodMultipleRuleTest {
-
- @Rule(order = Integer.MIN_VALUE)
- public final ExpectedException mExpectedException = ExpectedException.none();
-
- @Rule
- public final RavenwoodRule mRavenwood1 = new RavenwoodRule();
-
- @Rule
- public final RavenwoodRule mRavenwood2 = new RavenwoodRule();
-
- public RavenwoodMultipleRuleTest() {
- // We can't call it within the test method because the exception happens before
- // calling the method, so set it up here.
- if (RavenwoodConfig.isOnRavenwood()) {
- mExpectedException.expectMessage("Multiple nesting RavenwoodRule");
- }
- }
-
- @Test
- public void testMultipleRulesNotAllowed() {
- Assume.assumeTrue(RavenwoodConfig.isOnRavenwood());
- }
-}
diff --git a/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRuleValidationTest.java b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRuleValidationTest.java
new file mode 100644
index 000000000000..f9e73db23740
--- /dev/null
+++ b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRuleValidationTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ravenwoodtest.runnercallbacktests;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.SystemProperties;
+import android.platform.test.annotations.NoRavenizer;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+
+/**
+ * Test for RavenwoodRule.
+ */
+@NoRavenizer // This class shouldn't be executed with RavenwoodAwareTestRunner.
+public class RavenwoodRuleValidationTest extends RavenwoodRunnerTestBase {
+
+ public static class RuleInBaseClass {
+ static String PROPERTY_KEY = "debug.ravenwood.prop.in.base";
+ static String PROPERTY_VAL = "ravenwood";
+ @Rule
+ public final RavenwoodRule mRavenwood1 = new RavenwoodRule.Builder()
+ .setSystemPropertyImmutable(PROPERTY_KEY, PROPERTY_VAL).build();
+ }
+
+ /**
+ * Make sure that RavenwoodRule in a base class takes effect.
+ */
+ @RunWith(AndroidJUnit4.class)
+ // CHECKSTYLE:OFF
+ @Expected("""
+ testRunStarted: classes
+ testSuiteStarted: classes
+ testSuiteStarted: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRuleValidationTest$RuleInBaseClassSuccessTest
+ testStarted: testRuleInBaseClass(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRuleValidationTest$RuleInBaseClassSuccessTest)
+ testFinished: testRuleInBaseClass(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRuleValidationTest$RuleInBaseClassSuccessTest)
+ testSuiteFinished: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRuleValidationTest$RuleInBaseClassSuccessTest
+ testSuiteFinished: classes
+ testRunFinished: 1,0,0,0
+ """)
+ // CHECKSTYLE:ON
+ public static class RuleInBaseClassSuccessTest extends RuleInBaseClass {
+
+ @Test
+ public void testRuleInBaseClass() {
+ assertThat(SystemProperties.get(PROPERTY_KEY)).isEqualTo(PROPERTY_VAL);
+ }
+ }
+
+ /**
+ * Same as {@link RuleInBaseClass}, but the type of the rule field is not {@link RavenwoodRule}.
+ */
+ public abstract static class RuleWithDifferentTypeInBaseClass {
+ static String PROPERTY_KEY = "debug.ravenwood.prop.in.base.different.type";
+ static String PROPERTY_VAL = "ravenwood";
+ @Rule
+ public final TestRule mRavenwood1 = new RavenwoodRule.Builder()
+ .setSystemPropertyImmutable(PROPERTY_KEY, PROPERTY_VAL).build();
+ }
+
+ /**
+ * Make sure that RavenwoodRule in a base class takes effect, even if the field type is not
+ */
+ @RunWith(AndroidJUnit4.class)
+ // CHECKSTYLE:OFF
+ @Expected("""
+ testRunStarted: classes
+ testSuiteStarted: classes
+ testSuiteStarted: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRuleValidationTest$RuleWithDifferentTypeInBaseClassSuccessTest
+ testStarted: testRuleInBaseClass(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRuleValidationTest$RuleWithDifferentTypeInBaseClassSuccessTest)
+ testFinished: testRuleInBaseClass(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRuleValidationTest$RuleWithDifferentTypeInBaseClassSuccessTest)
+ testSuiteFinished: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRuleValidationTest$RuleWithDifferentTypeInBaseClassSuccessTest
+ testSuiteFinished: classes
+ testRunFinished: 1,0,0,0
+ """)
+ // CHECKSTYLE:ON
+ public static class RuleWithDifferentTypeInBaseClassSuccessTest extends RuleWithDifferentTypeInBaseClass {
+
+ @Test
+ public void testRuleInBaseClass() {
+ assertThat(SystemProperties.get(PROPERTY_KEY)).isEqualTo(PROPERTY_VAL);
+ }
+ }
+}
diff --git a/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerConfigValidationTest.java b/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerConfigValidationTest.java
deleted file mode 100644
index f94b98bc1fb8..000000000000
--- a/ravenwood/tests/coretest/test/com/android/ravenwoodtest/runnercallbacktests/RavenwoodRunnerConfigValidationTest.java
+++ /dev/null
@@ -1,484 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.ravenwoodtest.runnercallbacktests;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.platform.test.annotations.NoRavenizer;
-import android.platform.test.ravenwood.RavenwoodConfig;
-import android.platform.test.ravenwood.RavenwoodRule;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import org.junit.Ignore;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TestRule;
-import org.junit.runner.RunWith;
-
-
-/**
- * Test for @Config field extraction and validation.
- *
- * TODO(b/377765941) Most of the tests here will be obsolete and deleted with b/377765941, but
- * some of the tests may need to be re-implemented one way or another. (e.g. the package name
- * test.) Until that happens, we'll keep all tests here but add an {@code @Ignore} instead.
- */
-@NoRavenizer // This class shouldn't be executed with RavenwoodAwareTestRunner.
-public class RavenwoodRunnerConfigValidationTest extends RavenwoodRunnerTestBase {
- public abstract static class ConfigInBaseClass {
- static String PACKAGE_NAME = "com.ConfigInBaseClass";
-
- @RavenwoodConfig.Config
- public static RavenwoodConfig sConfig = new RavenwoodConfig.Builder()
- .setPackageName(PACKAGE_NAME).build();
- }
-
- /**
- * Make sure a config in the base class is detected.
- */
- @RunWith(AndroidJUnit4.class)
- // CHECKSTYLE:OFF
- @Expected("""
- testRunStarted: classes
- testSuiteStarted: classes
- testSuiteStarted: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigInBaseClassTest
- testStarted: test(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigInBaseClassTest)
- testFinished: test(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigInBaseClassTest)
- testSuiteFinished: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigInBaseClassTest
- testSuiteFinished: classes
- testRunFinished: 1,0,0,0
- """)
- // CHECKSTYLE:ON
- @Ignore // Package name is no longer set via config.
- public static class ConfigInBaseClassTest extends ConfigInBaseClass {
- @Test
- public void test() {
- assertThat(InstrumentationRegistry.getInstrumentation().getContext().getPackageName())
- .isEqualTo(PACKAGE_NAME);
- }
- }
-
- /**
- * Make sure a config in the base class is detected.
- */
- @RunWith(AndroidJUnit4.class)
- // CHECKSTYLE:OFF
- @Expected("""
- testRunStarted: classes
- testSuiteStarted: classes
- testSuiteStarted: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigOverridingTest
- testStarted: test(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigOverridingTest)
- testFinished: test(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigOverridingTest)
- testSuiteFinished: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigOverridingTest
- testSuiteFinished: classes
- testRunFinished: 1,0,0,0
- """)
- // CHECKSTYLE:ON
- @Ignore // Package name is no longer set via config.
- public static class ConfigOverridingTest extends ConfigInBaseClass {
- static String PACKAGE_NAME_OVERRIDE = "com.ConfigOverridingTest";
-
- @RavenwoodConfig.Config
- public static RavenwoodConfig sConfig = new RavenwoodConfig.Builder()
- .setPackageName(PACKAGE_NAME_OVERRIDE).build();
-
- @Test
- public void test() {
- assertThat(InstrumentationRegistry.getInstrumentation().getContext().getPackageName())
- .isEqualTo(PACKAGE_NAME_OVERRIDE);
- }
- }
-
- /**
- * Test to make sure that if a test has a config error, the failure would be reported from
- * each test method.
- */
- @RunWith(AndroidJUnit4.class)
- // CHECKSTYLE:OFF
- @Expected("""
- testRunStarted: classes
- testSuiteStarted: classes
- testStarted: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ErrorMustBeReportedFromEachTest)
- testFailure: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ErrorMustBeReportedFromEachTest.sConfig expected to be public static
- testFinished: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ErrorMustBeReportedFromEachTest)
- testSuiteFinished: classes
- testRunFinished: 1,1,0,0
- """)
- // CHECKSTYLE:ON
- public static class ErrorMustBeReportedFromEachTest {
- @RavenwoodConfig.Config
- private static RavenwoodConfig sConfig = // Invalid because it's private.
- new RavenwoodConfig.Builder().build();
-
- @Test
- public void testMethod1() {
- }
-
- @Test
- public void testMethod2() {
- }
-
- @Test
- public void testMethod3() {
- }
- }
-
- /**
- * Invalid because there are two @Config's.
- */
- @RunWith(AndroidJUnit4.class)
- // CHECKSTYLE:OFF
- @Expected("""
- testRunStarted: classes
- testSuiteStarted: classes
- testStarted: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$DuplicateConfigTest)
- testFailure: Class com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest.DuplicateConfigTest has multiple fields with @RavenwoodConfig.Config
- testFinished: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$DuplicateConfigTest)
- testSuiteFinished: classes
- testRunFinished: 1,1,0,0
- """)
- // CHECKSTYLE:ON
- public static class DuplicateConfigTest {
-
- @RavenwoodConfig.Config
- public static RavenwoodConfig sConfig1 =
- new RavenwoodConfig.Builder().build();
-
- @RavenwoodConfig.Config
- public static RavenwoodConfig sConfig2 =
- new RavenwoodConfig.Builder().build();
-
- @Test
- public void testConfig() {
- }
- }
-
- /**
- * @Config's must be static.
- */
- @RunWith(AndroidJUnit4.class)
- // CHECKSTYLE:OFF
- @Expected("""
- testRunStarted: classes
- testSuiteStarted: classes
- testStarted: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$NonStaticConfigTest)
- testFailure: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$NonStaticConfigTest.sConfig expected to be public static
- testFinished: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$NonStaticConfigTest)
- testSuiteFinished: classes
- testRunFinished: 1,1,0,0
- """)
- // CHECKSTYLE:ON
- public static class NonStaticConfigTest {
-
- @RavenwoodConfig.Config
- public RavenwoodConfig sConfig =
- new RavenwoodConfig.Builder().build();
-
- @Test
- public void testConfig() {
- }
- }
-
- /**
- * @Config's must be public.
- */
- @RunWith(AndroidJUnit4.class)
- // CHECKSTYLE:OFF
- @Expected("""
- testRunStarted: classes
- testSuiteStarted: classes
- testStarted: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$NonPublicConfigTest)
- testFailure: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$NonPublicConfigTest.sConfig expected to be public static
- testFinished: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$NonPublicConfigTest)
- testSuiteFinished: classes
- testRunFinished: 1,1,0,0
- """)
- // CHECKSTYLE:ON
- public static class NonPublicConfigTest {
-
- @RavenwoodConfig.Config
- RavenwoodConfig sConfig =
- new RavenwoodConfig.Builder().build();
-
- @Test
- public void testConfig() {
- }
- }
-
- /**
- * @Config's must be of type RavenwoodConfig.
- */
- @RunWith(AndroidJUnit4.class)
- // CHECKSTYLE:OFF
- @Expected("""
- testRunStarted: classes
- testSuiteStarted: classes
- testStarted: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WrongTypeConfigTest)
- testFailure: Field com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest.WrongTypeConfigTest.sConfig has @RavenwoodConfig.Config but type is not RavenwoodConfig
- testFinished: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WrongTypeConfigTest)
- testSuiteFinished: classes
- testRunFinished: 1,1,0,0
- """)
- // CHECKSTYLE:ON
- public static class WrongTypeConfigTest {
-
- @RavenwoodConfig.Config
- public static Object sConfig =
- new RavenwoodConfig.Builder().build();
-
- @Test
- public void testConfig() {
- }
-
- }
-
- /**
- * @Rule must be of type RavenwoodRule.
- */
- @RunWith(AndroidJUnit4.class)
- // CHECKSTYLE:OFF
- @Expected("""
- testRunStarted: classes
- testSuiteStarted: classes
- testSuiteStarted: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WrongTypeRuleTest
- testStarted: testConfig(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WrongTypeRuleTest)
- testFailure: If you have a RavenwoodRule in your test, make sure the field type is RavenwoodRule so Ravenwood can detect it.
- testFinished: testConfig(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WrongTypeRuleTest)
- testSuiteFinished: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WrongTypeRuleTest
- testSuiteFinished: classes
- testRunFinished: 1,1,0,0
- """)
- // CHECKSTYLE:ON
- public static class WrongTypeRuleTest {
-
- @Rule
- public TestRule mRule = new RavenwoodRule.Builder().build();
-
- @Test
- public void testConfig() {
- }
-
- }
-
- /**
- * Config can't be used with a (instance) Rule.
- */
- @RunWith(AndroidJUnit4.class)
- // CHECKSTYLE:OFF
- @Expected("""
- testRunStarted: classes
- testSuiteStarted: classes
- testStarted: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WithInstanceRuleTest)
- testFailure: RavenwoodConfig and RavenwoodRule cannot be used in the same class. Suggest migrating to RavenwoodConfig.
- testFinished: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WithInstanceRuleTest)
- testSuiteFinished: classes
- testRunFinished: 1,1,0,0
- """)
- // CHECKSTYLE:ON
- public static class WithInstanceRuleTest {
-
- @RavenwoodConfig.Config
- public static RavenwoodConfig sConfig =
- new RavenwoodConfig.Builder().build();
-
- @Rule
- public RavenwoodRule mRule = new RavenwoodRule.Builder().build();
-
- @Test
- public void testConfig() {
- }
- }
-
- /**
- * Config can't be used with a (static) Rule.
- */
- @RunWith(AndroidJUnit4.class)
- // CHECKSTYLE:OFF
- @Expected("""
- testRunStarted: classes
- testSuiteStarted: classes
- testStarted: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WithStaticRuleTest)
- testFailure: Failed to instantiate class androidx.test.ext.junit.runners.AndroidJUnit4
- testFinished: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$WithStaticRuleTest)
- testSuiteFinished: classes
- testRunFinished: 1,1,0,0
- """)
- // CHECKSTYLE:ON
- public static class WithStaticRuleTest {
-
- @RavenwoodConfig.Config
- public static RavenwoodConfig sConfig =
- new RavenwoodConfig.Builder().build();
-
- @Rule
- public static RavenwoodRule sRule = new RavenwoodRule.Builder().build();
-
- @Test
- public void testConfig() {
- }
- }
-
- @RunWith(AndroidJUnit4.class)
- // CHECKSTYLE:OFF
- @Expected("""
- testRunStarted: classes
- testSuiteStarted: classes
- testSuiteStarted: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$DuplicateRulesTest
- testStarted: testMultipleRulesNotAllowed(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$DuplicateRulesTest)
- testFailure: Multiple nesting RavenwoodRule's are detected in the same class, which is not supported.
- testFinished: testMultipleRulesNotAllowed(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$DuplicateRulesTest)
- testSuiteFinished: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$DuplicateRulesTest
- testSuiteFinished: classes
- testRunFinished: 1,1,0,0
- """)
- // CHECKSTYLE:ON
- public static class DuplicateRulesTest {
-
- @Rule
- public final RavenwoodRule mRavenwood1 = new RavenwoodRule();
-
- @Rule
- public final RavenwoodRule mRavenwood2 = new RavenwoodRule();
-
- @Test
- public void testMultipleRulesNotAllowed() {
- }
- }
-
- public static class RuleInBaseClass {
- static String PACKAGE_NAME = "com.RuleInBaseClass";
- @Rule
- public final RavenwoodRule mRavenwood1 = new RavenwoodRule.Builder()
- .setPackageName(PACKAGE_NAME).build();
- }
-
- /**
- * Make sure that RavenwoodRule in a base class takes effect.
- */
- @RunWith(AndroidJUnit4.class)
- // CHECKSTYLE:OFF
- @Expected("""
- testRunStarted: classes
- testSuiteStarted: classes
- testSuiteStarted: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$RuleInBaseClassSuccessTest
- testStarted: testRuleInBaseClass(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$RuleInBaseClassSuccessTest)
- testFinished: testRuleInBaseClass(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$RuleInBaseClassSuccessTest)
- testSuiteFinished: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$RuleInBaseClassSuccessTest
- testSuiteFinished: classes
- testRunFinished: 1,0,0,0
- """)
- // CHECKSTYLE:ON
- @Ignore // Package name is no longer set via config.
- public static class RuleInBaseClassSuccessTest extends RuleInBaseClass {
-
- @Test
- public void testRuleInBaseClass() {
- assertThat(InstrumentationRegistry.getInstrumentation().getContext().getPackageName())
- .isEqualTo(PACKAGE_NAME);
- }
- }
-
- /**
- * Make sure that having a config and a rule in a base class should fail.
- * RavenwoodRule.
- */
- @RunWith(AndroidJUnit4.class)
- // CHECKSTYLE:OFF
- @Expected("""
- testRunStarted: classes
- testSuiteStarted: classes
- testStarted: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigWithRuleInBaseClassTest)
- testFailure: RavenwoodConfig and RavenwoodRule cannot be used in the same class. Suggest migrating to RavenwoodConfig.
- testFinished: initializationError(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigWithRuleInBaseClassTest)
- testSuiteFinished: classes
- testRunFinished: 1,1,0,0
- """)
- // CHECKSTYLE:ON
- public static class ConfigWithRuleInBaseClassTest extends RuleInBaseClass {
- @RavenwoodConfig.Config
- public static RavenwoodConfig sConfig = new RavenwoodConfig.Builder().build();
-
- @Test
- public void test() {
- }
- }
-
- /**
- * Same as {@link RuleInBaseClass}, but the type of the rule field is not {@link RavenwoodRule}.
- */
- public abstract static class RuleWithDifferentTypeInBaseClass {
- static String PACKAGE_NAME = "com.RuleWithDifferentTypeInBaseClass";
- @Rule
- public final TestRule mRavenwood1 = new RavenwoodRule.Builder()
- .setPackageName(PACKAGE_NAME).build();
- }
-
- /**
- * Make sure that RavenwoodRule in a base class takes effect, even if the field type is not
- */
- @RunWith(AndroidJUnit4.class)
- // CHECKSTYLE:OFF
- @Expected("""
- testRunStarted: classes
- testSuiteStarted: classes
- testSuiteStarted: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$RuleWithDifferentTypeInBaseClassSuccessTest
- testStarted: testRuleInBaseClass(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$RuleWithDifferentTypeInBaseClassSuccessTest)
- testFailure: If you have a RavenwoodRule in your test, make sure the field type is RavenwoodRule so Ravenwood can detect it.
- testFinished: testRuleInBaseClass(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$RuleWithDifferentTypeInBaseClassSuccessTest)
- testSuiteFinished: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$RuleWithDifferentTypeInBaseClassSuccessTest
- testSuiteFinished: classes
- testRunFinished: 1,1,0,0
- """)
- // CHECKSTYLE:ON
- @Ignore // Package name is no longer set via config.
- public static class RuleWithDifferentTypeInBaseClassSuccessTest extends RuleWithDifferentTypeInBaseClass {
-
- @Test
- public void testRuleInBaseClass() {
- assertThat(InstrumentationRegistry.getInstrumentation().getContext().getPackageName())
- .isEqualTo(PACKAGE_NAME);
- }
- }
-
- /**
- * Make sure that having a config and a rule in a base class should fail, even if the field type is not
- * RavenwoodRule.
- */
- @RunWith(AndroidJUnit4.class)
- // CHECKSTYLE:OFF
- @Expected("""
- testRunStarted: classes
- testSuiteStarted: classes
- testSuiteStarted: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigWithRuleWithDifferentTypeInBaseClassTest
- testStarted: test(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigWithRuleWithDifferentTypeInBaseClassTest)
- testFailure: If you have a RavenwoodRule in your test, make sure the field type is RavenwoodRule so Ravenwood can detect it.
- testFinished: test(com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigWithRuleWithDifferentTypeInBaseClassTest)
- testSuiteFinished: com.android.ravenwoodtest.runnercallbacktests.RavenwoodRunnerConfigValidationTest$ConfigWithRuleWithDifferentTypeInBaseClassTest
- testSuiteFinished: classes
- testRunFinished: 1,1,0,0
- """)
- // CHECKSTYLE:ON
- public static class ConfigWithRuleWithDifferentTypeInBaseClassTest extends RuleWithDifferentTypeInBaseClass {
- @RavenwoodConfig.Config
- public static RavenwoodConfig sConfig = new RavenwoodConfig.Builder().build();
-
- @Test
- public void test() {
- }
- }
-}
diff --git a/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/IdentityTest.java b/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/IdentityTest.java
index 8e04b698c9d9..271c27f6ae93 100644
--- a/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/IdentityTest.java
+++ b/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/IdentityTest.java
@@ -15,7 +15,6 @@
*/
package com.android.ravenwoodtest.runtimetest;
-import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
import static android.os.Process.FIRST_APPLICATION_UID;
import static org.junit.Assert.assertEquals;
@@ -23,7 +22,6 @@ import static org.junit.Assert.assertEquals;
import android.os.Binder;
import android.os.Build;
import android.os.Process;
-import android.platform.test.ravenwood.RavenwoodConfig;
import android.system.Os;
import com.android.ravenwood.RavenwoodRuntimeState;
@@ -34,13 +32,6 @@ import org.junit.Test;
public class IdentityTest {
- @RavenwoodConfig.Config
- public static final RavenwoodConfig sConfig =
- new RavenwoodConfig.Builder()
- .setTargetSdkLevel(UPSIDE_DOWN_CAKE)
- .setProcessApp()
- .build();
-
@Test
public void testUid() {
assertEquals(FIRST_APPLICATION_UID, RavenwoodRuntimeState.sUid);
@@ -60,7 +51,7 @@ public class IdentityTest {
@Test
public void testTargetSdkLevel() {
assertEquals(Build.VERSION_CODES.CUR_DEVELOPMENT, RavenwoodRuntimeState.CUR_DEVELOPMENT);
- assertEquals(UPSIDE_DOWN_CAKE, RavenwoodRuntimeState.sTargetSdkLevel);
- assertEquals(UPSIDE_DOWN_CAKE, VMRuntime.getRuntime().getTargetSdkVersion());
+ assertEquals(RavenwoodRuntimeState.sTargetSdkLevel,
+ VMRuntime.getRuntime().getTargetSdkVersion());
}
}
diff --git a/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/SystemPropertyTest.java b/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/SystemPropertyTest.java
new file mode 100644
index 000000000000..70bf204ed823
--- /dev/null
+++ b/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/SystemPropertyTest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ravenwoodtest.runtimetest;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.os.SystemProperties;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runners.model.Statement;
+
+public class SystemPropertyTest {
+
+ private static final String PROP_KEY_1 = "debug.ravenwood.prop1";
+ private static final String PROP_VAL_1 = "ravenwood.1";
+ private static final String PROP_KEY_2 = "debug.ravenwood.prop2";
+ private static final String PROP_VAL_2 = "ravenwood.2";
+ private static final String PROP_KEY_3 = "debug.ravenwood.prop3";
+ private static final String PROP_VAL_3 = "ravenwood.3";
+ private static final String PROP_VAL_4 = "ravenwood.4";
+
+ @ClassRule(order = 0)
+ public static TestRule mCheckClassRule = (base, description) -> new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ assertTrue(SystemProperties.get(PROP_KEY_1).isEmpty());
+ assertTrue(SystemProperties.get(PROP_KEY_3).isEmpty());
+ try {
+ base.evaluate();
+ } finally {
+ assertTrue(SystemProperties.get(PROP_KEY_1).isEmpty());
+ assertTrue(SystemProperties.get(PROP_KEY_3).isEmpty());
+ }
+ }
+ };
+
+ @ClassRule(order = 1)
+ public static RavenwoodRule mClassRule = new RavenwoodRule.Builder()
+ .setSystemPropertyImmutable(PROP_KEY_1, PROP_VAL_1)
+ .setSystemPropertyImmutable(PROP_KEY_3, PROP_VAL_4)
+ .build();
+
+ @Rule(order = 0)
+ public TestRule mCheckRule = (base, description) -> new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ assertTrue(SystemProperties.get(PROP_KEY_2).isEmpty());
+ assertEquals(SystemProperties.get(PROP_KEY_3), PROP_VAL_4);
+ try {
+ base.evaluate();
+ } finally {
+ assertTrue(SystemProperties.get(PROP_KEY_2).isEmpty());
+ assertEquals(SystemProperties.get(PROP_KEY_3), PROP_VAL_4);
+ }
+ }
+ };
+
+ @Rule(order = 1)
+ public RavenwoodRule mRule = new RavenwoodRule.Builder()
+ .setSystemPropertyImmutable(PROP_KEY_2, PROP_VAL_2)
+ .setSystemPropertyImmutable(PROP_KEY_3, PROP_VAL_3)
+ .build();
+
+ @Test
+ public void testRavenwoodRuleSetProperty() {
+ assertEquals(SystemProperties.get(PROP_KEY_1), PROP_VAL_1);
+ assertEquals(SystemProperties.get(PROP_KEY_2), PROP_VAL_2);
+ assertEquals(SystemProperties.get(PROP_KEY_3), PROP_VAL_3);
+ }
+}
diff --git a/ravenwood/tests/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesTest.java b/ravenwood/tests/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesTest.java
index 4aae1e11b72e..e83a247bd769 100644
--- a/ravenwood/tests/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesTest.java
+++ b/ravenwood/tests/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesTest.java
@@ -24,8 +24,6 @@ import static org.junit.Assert.fail;
import android.content.Context;
import android.hardware.SerialManager;
import android.hardware.SerialManagerInternal;
-import android.platform.test.ravenwood.RavenwoodConfig;
-import android.platform.test.ravenwood.RavenwoodConfig.Config;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -42,12 +40,6 @@ import org.junit.runner.RunWith;
public class RavenwoodServicesTest {
private static final String TEST_VIRTUAL_PORT = "virtual:example";
- @Config
- public static final RavenwoodConfig sRavenwood = new RavenwoodConfig.Builder()
- .setProcessSystem()
- .setServicesRequired(SerialManager.class)
- .build();
-
private Context mContext;
@Before
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index d6fc6e461edc..7ef6aace1538 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -1180,18 +1180,8 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
}
private boolean anyServiceWantsGenericMotionEvent(MotionEvent event) {
- if (Flags.alwaysAllowObservingTouchEvents()) {
- final boolean isTouchEvent = event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN);
- if (isTouchEvent && !canShareGenericTouchEvent()) {
- return false;
- }
- final int eventSourceWithoutClass = event.getSource() & ~InputDevice.SOURCE_CLASS_MASK;
- return (mCombinedGenericMotionEventSources & eventSourceWithoutClass) != 0;
- }
- // Disable SOURCE_TOUCHSCREEN generic event interception if any service is performing
- // touch exploration.
- if (event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)
- && (mEnabledFeatures & FLAG_FEATURE_TOUCH_EXPLORATION) != 0) {
+ final boolean isTouchEvent = event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN);
+ if (isTouchEvent && !canShareGenericTouchEvent()) {
return false;
}
final int eventSourceWithoutClass = event.getSource() & ~InputDevice.SOURCE_CLASS_MASK;
@@ -1199,21 +1189,8 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo
}
private boolean anyServiceWantsToObserveMotionEvent(MotionEvent event) {
- if (Flags.alwaysAllowObservingTouchEvents()) {
- final int eventSourceWithoutClass = event.getSource() & ~InputDevice.SOURCE_CLASS_MASK;
- return (mCombinedMotionEventObservedSources & eventSourceWithoutClass) != 0;
- }
- // Disable SOURCE_TOUCHSCREEN generic event interception if any service is performing
- // touch exploration.
- if (event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)
- && (mEnabledFeatures & FLAG_FEATURE_TOUCH_EXPLORATION) != 0) {
- return false;
- }
final int eventSourceWithoutClass = event.getSource() & ~InputDevice.SOURCE_CLASS_MASK;
- return (mCombinedGenericMotionEventSources
- & mCombinedMotionEventObservedSources
- & eventSourceWithoutClass)
- != 0;
+ return (mCombinedMotionEventObservedSources & eventSourceWithoutClass) != 0;
}
private boolean canShareGenericTouchEvent() {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index c210e726fc12..71a0fc453a95 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -3797,13 +3797,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
|| (Flags.enableMagnificationMultipleFingerMultipleTapGesture()
&& userState.isMagnificationTwoFingerTripleTapEnabledLocked()));
- final boolean createConnectionForCurrentCapability =
- com.android.window.flags.Flags.alwaysDrawMagnificationFullscreenBorder()
- || (userState.getMagnificationCapabilitiesLocked()
- != Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
-
- final boolean connect = (shortcutEnabled && createConnectionForCurrentCapability)
- || userHasMagnificationServicesLocked(userState);
+ final boolean connect = shortcutEnabled || userHasMagnificationServicesLocked(userState);
getMagnificationConnectionManager().requestConnection(connect);
}
@@ -4852,8 +4846,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
getMagnificationConnectionManager().setConnection(connection);
- if (com.android.window.flags.Flags.alwaysDrawMagnificationFullscreenBorder()
- && connection == null
+ if (connection == null
&& mMagnificationController.isFullScreenMagnificationControllerInitialized()) {
// Since the connection does not exist, the system ui cannot provide the border
// implementation for fullscreen magnification. So we call reset to deactivate the
@@ -6548,8 +6541,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
// Only continue setting up the packages if the service has been initialized.
// See: b/340927041
- if (Flags.skipPackageChangeBeforeUserSwitch()
- && !mManagerService.isServiceInitializedLocked()) {
+ if (!mManagerService.isServiceInitializedLocked()) {
Slog.w(LOG_TAG,
"onSomePackagesChanged: service not initialized, skip the callback.");
return;
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
index d3d80e12313f..11b8ccb70dfb 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
@@ -699,10 +699,9 @@ public class FullScreenMagnificationController implements
if (!mRegistered) {
return false;
}
- // If the border implementation is on system ui side but the connection is not
+ // The border implementation is on system ui side but the connection is not
// established, the fullscreen magnification should not work.
- if (com.android.window.flags.Flags.alwaysDrawMagnificationFullscreenBorder()
- && !mMagnificationConnectionStateSupplier.get()) {
+ if (!mMagnificationConnectionStateSupplier.get()) {
return false;
}
if (DEBUG) {
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
index 51c4305061f8..058b2be5f4b3 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
@@ -51,7 +51,6 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.server.LocalServices;
import com.android.server.accessibility.AccessibilityManagerService;
import com.android.server.wm.WindowManagerInternal;
-import com.android.window.flags.Flags;
import java.util.concurrent.Executor;
@@ -634,10 +633,8 @@ public class MagnificationController implements MagnificationConnectionManager.C
@Override
public void onFullScreenMagnificationActivationState(int displayId, boolean activated) {
- if (Flags.alwaysDrawMagnificationFullscreenBorder()) {
- getMagnificationConnectionManager()
- .onFullscreenMagnificationActivationChanged(displayId, activated);
- }
+ getMagnificationConnectionManager()
+ .onFullscreenMagnificationActivationChanged(displayId, activated);
if (activated) {
synchronized (mLock) {
diff --git a/services/backup/java/com/android/server/backup/BackupAgentConnectionManager.java b/services/backup/java/com/android/server/backup/BackupAgentConnectionManager.java
index 5e4bab15952d..02f186557ca4 100644
--- a/services/backup/java/com/android/server/backup/BackupAgentConnectionManager.java
+++ b/services/backup/java/com/android/server/backup/BackupAgentConnectionManager.java
@@ -41,6 +41,7 @@ import android.os.UserHandle;
import android.util.ArraySet;
import android.util.Slog;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.LocalServices;
import com.android.server.backup.internal.LifecycleOperationStorage;
@@ -71,8 +72,10 @@ public class BackupAgentConnectionManager {
// Activity Manager; use this lock object to signal when a requested binding has
// completed.
private final Object mAgentConnectLock = new Object();
- private IBackupAgent mConnectedAgent;
- private volatile boolean mConnecting;
+ @GuardedBy("mAgentConnectLock")
+ @Nullable
+ private BackupAgentConnection mCurrentConnection;
+
private final ArraySet<String> mRestoreNoRestrictedModePackages = new ArraySet<>();
private final ArraySet<String> mBackupNoRestrictedModePackages = new ArraySet<>();
@@ -96,8 +99,18 @@ public class BackupAgentConnectionManager {
mUserIdMsg = "[UserID:" + userId + "] ";
}
+ private static final class BackupAgentConnection {
+ public final ApplicationInfo appInfo;
+ public IBackupAgent backupAgent;
+ public boolean connecting = true; // Assume we are trying to connect on creation.
+
+ private BackupAgentConnection(ApplicationInfo appInfo) {
+ this.appInfo = appInfo;
+ }
+ }
+
/**
- * Fires off a backup agent, blocking until it attaches (and ActivityManager will call
+ * Fires off a backup agent, blocking until it attaches (i.e. ActivityManager calls
* {@link #agentConnected(String, IBinder)}) or until this operation times out.
*
* @param mode a {@code BACKUP_MODE} from {@link android.app.ApplicationThreadConstants}.
@@ -105,48 +118,56 @@ public class BackupAgentConnectionManager {
@Nullable
public IBackupAgent bindToAgentSynchronous(ApplicationInfo app, int mode,
@BackupAnnotations.BackupDestination int backupDestination) {
- IBackupAgent agent = null;
synchronized (mAgentConnectLock) {
- mConnecting = true;
- mConnectedAgent = null;
boolean useRestrictedMode = shouldUseRestrictedBackupModeForPackage(mode,
app.packageName);
+ if (mCurrentConnection != null) {
+ Slog.e(TAG, mUserIdMsg + "binding to new agent before unbinding from old one: "
+ + mCurrentConnection.appInfo.packageName);
+ }
+ mCurrentConnection = new BackupAgentConnection(app);
+
+ // bindBackupAgent() is an async API. It will kick off the app's process and call
+ // agentConnected() when it receives the agent from the app.
+ boolean startedBindSuccessfully = false;
try {
- if (mActivityManager.bindBackupAgent(app.packageName, mode, mUserId,
- backupDestination, useRestrictedMode)) {
- Slog.d(TAG, mUserIdMsg + "awaiting agent for " + app);
-
- // success; wait for the agent to arrive
- // only wait 10 seconds for the bind to happen
- long timeoutMark = System.currentTimeMillis() + 10 * 1000;
- while (mConnecting && mConnectedAgent == null && (System.currentTimeMillis()
- < timeoutMark)) {
- try {
- mAgentConnectLock.wait(5000);
- } catch (InterruptedException e) {
- // just bail
- Slog.w(TAG, mUserIdMsg + "Interrupted: " + e);
- mConnecting = false;
- mConnectedAgent = null;
- }
- }
+ startedBindSuccessfully = mActivityManager.bindBackupAgent(app.packageName, mode,
+ mUserId, backupDestination, useRestrictedMode);
+ } catch (RemoteException e) {
+ // can't happen - ActivityManager is local
+ }
- // if we timed out with no connect, abort and move on
- if (mConnecting) {
- Slog.w(TAG, mUserIdMsg + "Timeout waiting for agent " + app);
- mConnectedAgent = null;
+ if (!startedBindSuccessfully) {
+ Slog.w(TAG, mUserIdMsg + "bind request failed for " + app.packageName);
+ mCurrentConnection = null;
+ } else {
+ Slog.d(TAG, mUserIdMsg + "awaiting agent for " + app.packageName);
+
+ // Wait 10 seconds for the agent and then time out if we still haven't bound to it.
+ long timeoutMark = System.currentTimeMillis() + 10 * 1000;
+ while (mCurrentConnection != null && mCurrentConnection.connecting && (
+ System.currentTimeMillis() < timeoutMark)) {
+ try {
+ mAgentConnectLock.wait(5000);
+ } catch (InterruptedException e) {
+ Slog.w(TAG, mUserIdMsg + "Interrupted: " + e);
+ mCurrentConnection = null;
}
- Slog.i(TAG, mUserIdMsg + "got agent " + mConnectedAgent);
- agent = mConnectedAgent;
}
- } catch (RemoteException e) {
- // can't happen - ActivityManager is local
}
- }
- if (agent == null) {
+
+ if (mCurrentConnection != null) {
+ if (!mCurrentConnection.connecting) {
+ return mCurrentConnection.backupAgent;
+ }
+ // If we are still connecting, we've timed out.
+ Slog.w(TAG, mUserIdMsg + "Timeout waiting for agent " + app);
+ mCurrentConnection = null;
+ }
+
mActivityManagerInternal.clearPendingBackup(mUserId);
+ return null;
}
- return agent;
}
/**
@@ -154,30 +175,49 @@ public class BackupAgentConnectionManager {
* It will tell the app to destroy the agent.
*/
public void unbindAgent(ApplicationInfo app) {
- try {
- mActivityManager.unbindBackupAgent(app);
- } catch (RemoteException e) {
- // Can't happen - activity manager is local
+ synchronized (mAgentConnectLock) {
+ if (mCurrentConnection == null) {
+ Slog.w(TAG, mUserIdMsg + "unbindAgent but no current connection");
+ } else if (!mCurrentConnection.appInfo.packageName.equals(app.packageName)) {
+ Slog.w(TAG, mUserIdMsg + "unbindAgent for unexpected package: " + app.packageName
+ + " expected: " + mCurrentConnection.appInfo.packageName);
+ } else {
+ mCurrentConnection = null;
+ }
+
+ // Even if we weren't expecting to be bound to this agent, we should still call
+ // ActivityManager just in case. It will ignore the call if it also wasn't expecting it.
+ try {
+ mActivityManager.unbindBackupAgent(app);
+ } catch (RemoteException e) {
+ // Can't happen - activity manager is local
+ }
}
}
/**
* Callback: a requested backup agent has been instantiated. This should only be called from
- * the
- * {@link ActivityManager} when it's telling us that an agent is ready after a call to
+ * the {@link ActivityManager} when it's telling us that an agent is ready after a call to
* {@link #bindToAgentSynchronous(ApplicationInfo, int, int)}.
*/
public void agentConnected(String packageName, IBinder agentBinder) {
synchronized (mAgentConnectLock) {
- if (getCallingUid() == android.os.Process.SYSTEM_UID) {
- Slog.d(TAG,
- mUserIdMsg + "agentConnected pkg=" + packageName + " agent=" + agentBinder);
- mConnectedAgent = IBackupAgent.Stub.asInterface(agentBinder);
- mConnecting = false;
- } else {
+ if (getCallingUid() != Process.SYSTEM_UID) {
Slog.w(TAG, mUserIdMsg + "Non-system process uid=" + getCallingUid()
+ " claiming agent connected");
+ return;
}
+
+ Slog.d(TAG, mUserIdMsg + "agentConnected pkg=" + packageName + " agent=" + agentBinder);
+ if (mCurrentConnection == null) {
+ Slog.w(TAG, mUserIdMsg + "was not expecting connection");
+ } else if (!mCurrentConnection.appInfo.packageName.equals(packageName)) {
+ Slog.w(TAG, mUserIdMsg + "got agent for unexpected package=" + packageName);
+ } else {
+ mCurrentConnection.backupAgent = IBackupAgent.Stub.asInterface(agentBinder);
+ mCurrentConnection.connecting = false;
+ }
+
mAgentConnectLock.notifyAll();
}
}
@@ -189,16 +229,22 @@ public class BackupAgentConnectionManager {
*/
public void agentDisconnected(String packageName) {
synchronized (mAgentConnectLock) {
- if (getCallingUid() == Process.SYSTEM_UID) {
- mConnectedAgent = null;
- mConnecting = false;
- } else {
+ if (getCallingUid() != Process.SYSTEM_UID) {
Slog.w(TAG, mUserIdMsg + "Non-system process uid=" + getCallingUid()
+ " claiming agent disconnected");
+ return;
}
+
Slog.w(TAG, mUserIdMsg + "agentDisconnected: the backup agent for " + packageName
+ " died: cancel current operations");
+ // Only abort the current connection if the agent we were expecting or already
+ // connected to has disconnected.
+ if (mCurrentConnection != null && mCurrentConnection.appInfo.packageName.equals(
+ packageName)) {
+ mCurrentConnection = null;
+ }
+
// Offload operation cancellation off the main thread as the cancellation callbacks
// might call out to BackupTransport. Other operations started on the same package
// before the cancellation callback has executed will also be cancelled by the callback.
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 3dcca1433dec..4cf17ae3984d 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -69,6 +69,7 @@ import android.service.battery.BatteryServiceDumpProto;
import android.sysprop.PowerProperties;
import android.util.EventLog;
import android.util.Slog;
+import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.VisibleForTesting;
@@ -303,6 +304,17 @@ public final class BatteryService extends SystemService {
*/
@VisibleForTesting
public long mLastBroadcastVoltageUpdateTime;
+ /**
+ * Time when the max charging current was updated last by HAL and we sent the
+ * {@link Intent#ACTION_BATTERY_CHANGED} broadcast.
+ * Note: This value is used to rate limit the {@link Intent#ACTION_BATTERY_CHANGED} broadcast
+ * so it is possible that max current was updated but we did not send the broadcast so in that
+ * case we do not update the time.
+ */
+ @VisibleForTesting
+ public long mLastBroadcastMaxChargingCurrentUpdateTime;
+
+ private boolean mIsFirstBatteryChangedUpdate = true;
private Led mLed;
@@ -350,16 +362,21 @@ public final class BatteryService extends SystemService {
private static final int ABSOLUTE_DECI_CELSIUS_DIFF_FOR_TEMP_UPDATE = 10;
/**
* This value is used to rate limit the {@link Intent#ACTION_BATTERY_CHANGED} broadcast. We
- * only send the broadcast if the last voltage was updated at least 20s seconds back and has a
+ * only send the broadcast if the last voltage was updated at least 20 seconds back and has a
* fluctuation of at least 1%.
*/
private static final int TIME_DIFF_FOR_VOLTAGE_UPDATE_MS = 20000;
/**
* The value is used to rate limit the {@link Intent#ACTION_BATTERY_CHANGED} broadcast. We
- * only send the broadcast if the last voltage was updated at least 20s seconds back and has a
+ * only send the broadcast if the last voltage was updated at least 20 seconds back and has a
* fluctuation of at least 1%.
*/
private static final float BASE_POINT_DIFF_FOR_VOLTAGE_UPDATE = 0.01f;
+ /**
+ * This value is used to rate limit the {@link Intent#ACTION_BATTERY_CHANGED} broadcast. We
+ * only send the broadcast if the last max charging current was updated at least 5 seconds back.
+ */
+ private static final int TIME_DIFF_FOR_MAX_CHARGING_CURRENT_UPDATE_MS = 5000;
private final Handler.Callback mLocalCallback = msg -> {
switch (msg.what) {
@@ -1252,8 +1269,10 @@ public final class BatteryService extends SystemService {
if (!com.android.server.flags.Flags.rateLimitBatteryChangedBroadcast()) {
return false;
}
- if (mLastBroadcastBatteryVoltage == 0 || mLastBroadcastBatteryTemperature == 0) {
+ if (mIsFirstBatteryChangedUpdate) {
mLastBroadcastVoltageUpdateTime = SystemClock.elapsedRealtime();
+ mLastBroadcastMaxChargingCurrentUpdateTime = SystemClock.elapsedRealtime();
+ mIsFirstBatteryChangedUpdate = false;
return false;
}
@@ -1261,13 +1280,14 @@ public final class BatteryService extends SystemService {
mLastBroadcastBatteryVoltage != mHealthInfo.batteryVoltageMillivolts;
final boolean temperatureUpdated =
mLastBroadcastBatteryTemperature != mHealthInfo.batteryTemperatureTenthsCelsius;
+ final boolean maxChargingCurrentUpdated =
+ mLastBroadcastMaxChargingCurrent != mHealthInfo.maxChargingCurrentMicroamps;
final boolean otherStatesUpdated = forceUpdate
|| mHealthInfo.batteryStatus != mLastBroadcastBatteryStatus
|| mHealthInfo.batteryHealth != mLastBroadcastBatteryHealth
|| mHealthInfo.batteryPresent != mLastBroadcastBatteryPresent
|| mHealthInfo.batteryLevel != mLastBroadcastBatteryLevel
|| mPlugType != mLastBroadcastPlugType
- || mHealthInfo.maxChargingCurrentMicroamps != mLastBroadcastMaxChargingCurrent
|| mHealthInfo.maxChargingVoltageMicrovolts != mLastBroadcastMaxChargingVoltage
|| mInvalidCharger != mLastBroadcastInvalidCharger
|| mHealthInfo.batteryCycleCount != mLastBroadcastBatteryCycleCount
@@ -1280,6 +1300,9 @@ public final class BatteryService extends SystemService {
if (voltageUpdated) {
mLastBroadcastVoltageUpdateTime = SystemClock.elapsedRealtime();
}
+ if (maxChargingCurrentUpdated) {
+ mLastBroadcastMaxChargingCurrentUpdateTime = SystemClock.elapsedRealtime();
+ }
return false;
}
@@ -1295,6 +1318,9 @@ public final class BatteryService extends SystemService {
>= TIME_DIFF_FOR_VOLTAGE_UPDATE_MS) {
mLastBroadcastVoltageUpdateTime = SystemClock.elapsedRealtime();
+ if (maxChargingCurrentUpdated) {
+ mLastBroadcastMaxChargingCurrentUpdateTime = SystemClock.elapsedRealtime();
+ }
return false;
}
@@ -1307,6 +1333,20 @@ public final class BatteryService extends SystemService {
if (voltageUpdated) {
mLastBroadcastVoltageUpdateTime = SystemClock.elapsedRealtime();
}
+ if (maxChargingCurrentUpdated) {
+ mLastBroadcastMaxChargingCurrentUpdateTime = SystemClock.elapsedRealtime();
+ }
+ return false;
+ }
+
+ if (maxChargingCurrentUpdated
+ && SystemClock.elapsedRealtime() - mLastBroadcastMaxChargingCurrentUpdateTime
+ >= TIME_DIFF_FOR_MAX_CHARGING_CURRENT_UPDATE_MS) {
+ mLastBroadcastMaxChargingCurrentUpdateTime = SystemClock.elapsedRealtime();
+
+ if (voltageUpdated) {
+ mLastBroadcastVoltageUpdateTime = SystemClock.elapsedRealtime();
+ }
return false;
}
@@ -1615,6 +1655,9 @@ public final class BatteryService extends SystemService {
pw.println(" Wireless powered: " + mHealthInfo.chargerWirelessOnline);
pw.println(" Dock powered: " + mHealthInfo.chargerDockOnline);
pw.println(" Max charging current: " + mHealthInfo.maxChargingCurrentMicroamps);
+ pw.println(" Time when the latest updated value of the Max charging current was"
+ + " sent via battery changed broadcast: "
+ + TimeUtils.formatDuration(mLastBroadcastMaxChargingCurrentUpdateTime));
pw.println(" Max charging voltage: " + mHealthInfo.maxChargingVoltageMicrovolts);
pw.println(" Charge counter: " + mHealthInfo.batteryChargeCounterUah);
pw.println(" status: " + mHealthInfo.batteryStatus);
@@ -1624,7 +1667,8 @@ public final class BatteryService extends SystemService {
pw.println(" scale: " + BATTERY_SCALE);
pw.println(" voltage: " + mHealthInfo.batteryVoltageMillivolts);
pw.println(" Time when the latest updated value of the voltage was sent via "
- + "battery changed broadcast: " + mLastBroadcastVoltageUpdateTime);
+ + "battery changed broadcast: "
+ + TimeUtils.formatDuration(mLastBroadcastVoltageUpdateTime));
pw.println(" The last voltage value sent via the battery changed broadcast: "
+ mLastBroadcastBatteryVoltage);
pw.println(" temperature: " + mHealthInfo.batteryTemperatureTenthsCelsius);
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index aabbd3bf49ae..0286f7b0b73d 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -1580,10 +1580,20 @@ public class BinaryTransparencyService extends SystemService {
}
private void registerBicCallback() {
+ if(!com.android.server.flags.Flags.optionalBackgroundInstallControl()) {
+ Slog.d(TAG, "BICS is disabled for this device, skipping registration.");
+ return;
+ }
IBackgroundInstallControlService iBics =
IBackgroundInstallControlService.Stub.asInterface(
ServiceManager.getService(
Context.BACKGROUND_INSTALL_CONTROL_SERVICE));
+ if(iBics == null) {
+ Slog.e(TAG, "Failed to register BackgroundInstallControl callback, either "
+ + "background install control service does not exist or disabled on this "
+ + "build.");
+ return;
+ }
try {
iBics.registerBackgroundInstallCallback(
new BicCallbackHandler(mServiceImpl));
diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING
index ce66dc3c76cb..8da835896bd3 100644
--- a/services/core/java/com/android/server/TEST_MAPPING
+++ b/services/core/java/com/android/server/TEST_MAPPING
@@ -176,6 +176,10 @@
"include-filter": "com.android.server.wm.BackgroundActivityStart*"
}
]
+ },
+ {
+ "name": "FrameworksMockingServicesTests_service_batteryServiceTest",
+ "file_patterns": ["BatteryService\\.java"]
}
]
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index fa40283d43e1..37d058b06b99 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -857,6 +857,7 @@ final class ActivityManagerShellCommand extends ShellCommand {
}
options.setDismissKeyguardIfInsecure();
}
+ intent.collectExtraIntentKeys();
if (mWaitOption) {
result = mInternal.startActivityAndWait(null, SHELL_PACKAGE_NAME, null, intent,
mimeType, null, null, 0, mStartFlags, profilerInfo,
@@ -975,6 +976,7 @@ final class ActivityManagerShellCommand extends ShellCommand {
}
pw.println("Starting service: " + intent);
pw.flush();
+ intent.collectExtraIntentKeys();
ComponentName cn = mInterface.startService(null, intent, intent.getType(),
asForeground, SHELL_PACKAGE_NAME, null, mUserId);
if (cn == null) {
@@ -1007,6 +1009,7 @@ final class ActivityManagerShellCommand extends ShellCommand {
}
pw.println("Stopping service: " + intent);
pw.flush();
+ intent.collectExtraIntentKeys();
int result = mInterface.stopService(null, intent, intent.getType(), mUserId);
if (result == 0) {
err.println("Service not stopped: was not running.");
@@ -1404,6 +1407,12 @@ final class ActivityManagerShellCommand extends ShellCommand {
heapFile = "/data/local/tmp/heapdump-" + logNameTimeString + ".prof";
}
+ String argAfterHeapFile = getNextArg();
+ if (argAfterHeapFile != null) {
+ err.println("Error: Arguments cannot be placed after the heap file");
+ return -1;
+ }
+
// Writes an error message to stderr on failure
ParcelFileDescriptor fd = openFileForSystem(heapFile, "w");
if (fd == null) {
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index d5bd05764f38..400ebfde1741 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -1126,20 +1126,8 @@ public final class BatteryStatsService extends IBatteryStats.Stub
break;
}
case FrameworkStatsLog.BATTERY_USAGE_STATS_SINCE_RESET_USING_POWER_PROFILE_MODEL:
- if (Flags.disableCompositeBatteryUsageStatsAtoms()) {
- return StatsManager.PULL_SKIP;
- }
+ return StatsManager.PULL_SKIP;
- final BatteryUsageStatsQuery queryPowerProfile =
- new BatteryUsageStatsQuery.Builder()
- .setMaxStatsAgeMs(0)
- .includeProcessStateData()
- .includeVirtualUids()
- .powerProfileModeledOnly()
- .includePowerModels()
- .build();
- bus = getBatteryUsageStats(List.of(queryPowerProfile)).get(0);
- break;
case FrameworkStatsLog.BATTERY_USAGE_STATS_BEFORE_RESET: {
if (Flags.disableCompositeBatteryUsageStatsAtoms()) {
return StatsManager.PULL_SKIP;
@@ -3184,7 +3172,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub
}
}
- private void dumpUsageStats(FileDescriptor fd, PrintWriter pw, int model,
+ private void dumpUsageStats(FileDescriptor fd, PrintWriter pw,
boolean proto, boolean accumulated) {
awaitCompletion();
syncStats("dump", BatteryExternalStatsWorker.UPDATE_ALL);
@@ -3196,9 +3184,6 @@ public final class BatteryStatsService extends IBatteryStats.Stub
if (Flags.batteryUsageStatsByPowerAndScreenState()) {
builder.includeScreenStateData().includePowerStateData();
}
- if (model == BatteryConsumer.POWER_MODEL_POWER_PROFILE) {
- builder.powerProfileModeledOnly();
- }
if (accumulated) {
builder.accumulated();
}
@@ -3393,7 +3378,6 @@ public final class BatteryStatsService extends IBatteryStats.Stub
dumpPowerProfile(pw);
return;
} else if ("--usage".equals(arg)) {
- int model = BatteryConsumer.POWER_MODEL_UNDEFINED;
boolean proto = false;
boolean accumulated = false;
for (int j = i + 1; j < args.length; j++) {
@@ -3401,29 +3385,12 @@ public final class BatteryStatsService extends IBatteryStats.Stub
case "--proto":
proto = true;
break;
- case "--model": {
- if (j + 1 < args.length) {
- j++;
- if ("power-profile".equals(args[j])) {
- model = BatteryConsumer.POWER_MODEL_POWER_PROFILE;
- } else {
- pw.println("Unknown power model: " + args[j]);
- dumpHelp(pw);
- return;
- }
- } else {
- pw.println("--model without a value");
- dumpHelp(pw);
- return;
- }
- break;
- }
case "--accumulated":
accumulated = true;
break;
}
}
- dumpUsageStats(fd, pw, model, proto, accumulated);
+ dumpUsageStats(fd, pw, proto, accumulated);
return;
} else if ("--wakeups".equals(arg)) {
mCpuWakeupStats.dump(new IndentingPrintWriter(pw, " "),
diff --git a/services/core/java/com/android/server/am/OWNERS b/services/core/java/com/android/server/am/OWNERS
index ab7cd5f29660..1a6051b6ac9c 100644
--- a/services/core/java/com/android/server/am/OWNERS
+++ b/services/core/java/com/android/server/am/OWNERS
@@ -66,6 +66,9 @@ per-file CarUserSwitchingDialog.java = file:platform/packages/services/Car:/OWNE
# Activity Security
per-file ActivityManager* = file:/ACTIVITY_SECURITY_OWNERS
+# Aconfig Flags
+per-file flags.aconfig = yamasani@google.com, bills@google.com, nalini@google.com
+
# Londoners
michaelwr@google.com #{LAST_RESORT_SUGGESTION}
narayan@google.com #{LAST_RESORT_SUGGESTION}
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 78c4f74f3afa..7660c154efd5 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -140,6 +140,8 @@ public class SettingsToPropertiesMapper {
// The list is sorted.
@VisibleForTesting
static final String[] sDeviceConfigAconfigScopes = new String[] {
+ "aaos_audio_triage",
+ "aaos_power_triage",
"aaos_sdv",
"accessibility",
"android_core_networking",
@@ -533,9 +535,8 @@ public class SettingsToPropertiesMapper {
* @param packageName the package of the flag
* @param flagName the name of the flag
* @param immediate if true, clear immediately; otherwise, clear on next reboot
- *
- * @hide
*/
+ @VisibleForTesting
public static void writeFlagOverrideRemovalRequest(
ProtoOutputStream proto, String packageName, String flagName, boolean immediate) {
long msgsToken = proto.start(StorageRequestMessages.MSGS);
@@ -591,7 +592,8 @@ public class SettingsToPropertiesMapper {
* apply flag local override in aconfig new storage
* @param props
*/
- static void setLocalOverridesInNewStorage(DeviceConfig.Properties props) {
+ @VisibleForTesting
+ public static void setLocalOverridesInNewStorage(DeviceConfig.Properties props) {
int num_requests = 0;
ProtoOutputStream requests = new ProtoOutputStream();
for (String flagName : props.getKeyset()) {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 5c2eb5cf1086..c6317bd29824 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -4954,31 +4954,24 @@ public class AudioService extends IAudioService.Stub
}
final Set<Integer> deviceTypes = getDeviceSetForStreamDirect(streamType);
+ Set<Integer> absVolumeDeviceTypes = new ArraySet<>(
+ AudioSystem.DEVICE_OUT_ALL_A2DP_SET);
+ absVolumeDeviceTypes.addAll(mAbsVolumeMultiModeCaseDevices);
- final Set<Integer> a2dpDevices = AudioSystem.intersectionAudioDeviceTypes(
- AudioSystem.DEVICE_OUT_ALL_A2DP_SET, deviceTypes);
- if (!a2dpDevices.isEmpty()) {
- int index = getStreamVolume(streamType,
- a2dpDevices.toArray(new Integer[0])[0].intValue());
- mDeviceBroker.postSetAvrcpAbsoluteVolumeIndex(index);
- }
-
- final Set<Integer> absVolumeMultiModeCaseDevices =
- AudioSystem.intersectionAudioDeviceTypes(
- mAbsVolumeMultiModeCaseDevices, deviceTypes);
- if (absVolumeMultiModeCaseDevices.isEmpty()) {
+ final Set<Integer> absVolumeDevices =
+ AudioSystem.intersectionAudioDeviceTypes(absVolumeDeviceTypes, deviceTypes);
+ if (absVolumeDevices.isEmpty()) {
return;
}
- if (absVolumeMultiModeCaseDevices.size() > 1) {
+ if (absVolumeDevices.size() > 1) {
Log.w(TAG, "onUpdateContextualVolumes too many active devices: "
- + absVolumeMultiModeCaseDevices.stream().map(AudioSystem::getOutputDeviceName)
+ + absVolumeDevices.stream().map(AudioSystem::getOutputDeviceName)
.collect(Collectors.joining(","))
+ ", for stream: " + streamType);
return;
}
- final int device = absVolumeMultiModeCaseDevices.toArray(new Integer[0])[0].intValue();
-
+ final int device = absVolumeDevices.toArray(new Integer[0])[0].intValue();
final int index = getStreamVolume(streamType, device);
if (DEBUG_VOL) {
@@ -4992,6 +4985,8 @@ public class AudioService extends IAudioService.Stub
getVssForStreamOrDefault(streamType).getMaxIndex(), streamType);
} else if (device == AudioSystem.DEVICE_OUT_HEARING_AID) {
mDeviceBroker.postSetHearingAidVolumeIndex(index * 10, streamType);
+ } else if (AudioSystem.DEVICE_OUT_ALL_A2DP_SET.contains(device)) {
+ mDeviceBroker.postSetAvrcpAbsoluteVolumeIndex(index);
} else {
return;
}
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index a2200c9f8bf5..1c01fb9f19e0 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -17,7 +17,7 @@
package com.android.server.audio;
import static android.media.AudioPlaybackConfiguration.EXTRA_PLAYER_EVENT_MUTE;
-import static android.media.AudioPlaybackConfiguration.MUTED_BY_APP_OPS;
+import static android.media.AudioPlaybackConfiguration.MUTED_BY_OP_PLAY_AUDIO;
import static android.media.AudioPlaybackConfiguration.MUTED_BY_CLIENT_VOLUME;
import static android.media.AudioPlaybackConfiguration.MUTED_BY_MASTER;
import static android.media.AudioPlaybackConfiguration.MUTED_BY_PORT_VOLUME;
@@ -1377,8 +1377,8 @@ public final class PlaybackActivityMonitor
if ((eventValue & MUTED_BY_STREAM_MUTED) != 0) {
builder.append("streamMute ");
}
- if ((eventValue & MUTED_BY_APP_OPS) != 0) {
- builder.append("appOps ");
+ if ((eventValue & MUTED_BY_OP_PLAY_AUDIO) != 0) {
+ builder.append("opPlayAudio ");
}
if ((eventValue & MUTED_BY_CLIENT_VOLUME) != 0) {
builder.append("clientVolume ");
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 e273c6862fe0..45106f54cb9f 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -257,6 +257,11 @@ public class DisplayManagerFlags {
Flags::displayListenerPerformanceImprovements
);
+ private final FlagState mSubscribeGranularDisplayEvents = new FlagState(
+ Flags.FLAG_SUBSCRIBE_GRANULAR_DISPLAY_EVENTS,
+ Flags::subscribeGranularDisplayEvents
+ );
+
/**
* @return {@code true} if 'port' is allowed in display layout configuration file.
*/
@@ -552,6 +557,13 @@ public class DisplayManagerFlags {
}
/**
+ * @return {@code true} if the flag for subscribing to granular display events is enabled
+ */
+ public boolean isSubscribeGranularDisplayEventsEnabled() {
+ return mSubscribeGranularDisplayEvents.isEnabled();
+ }
+
+ /**
* dumps all flagstates
* @param pw printWriter
*/
@@ -605,6 +617,7 @@ public class DisplayManagerFlags {
pw.println(" " + mGetSupportedRefreshRatesFlagState);
pw.println(" " + mEnablePluginManagerFlagState);
pw.println(" " + mDisplayListenerPerformanceImprovementsFlagState);
+ pw.println(" " + mSubscribeGranularDisplayEvents);
}
private static class FlagState {
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 e7ea868ca04f..3976d01d806d 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
@@ -478,3 +478,14 @@ flag {
bug: "378385869"
is_fixed_read_only: true
}
+
+flag {
+ name: "subscribe_granular_display_events"
+ namespace: "display_manager"
+ description: "Enable subscription to granular display change events."
+ bug: "379250634"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/services/core/java/com/android/server/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java
index 0e7d2b631833..a06f9ef634d1 100644
--- a/services/core/java/com/android/server/hdmi/Constants.java
+++ b/services/core/java/com/android/server/hdmi/Constants.java
@@ -524,6 +524,13 @@ final class Constants {
static final String PROPERTY_DISABLE_CEC_ON_STANDBY_IN_LOW_ENERGY_MODE =
"persist.sys.hdmi.property_disable_cec_on_standby_in_low_energy_mode";
+ /**
+ * Property that checks if CEC was manually enabled by the user in offline mode. With the help
+ * of this property we avoid turning off CEC when the device goes to sleep and if the device
+ * is in low energy mode.
+ */
+ static final String PROPERTY_USER_ACTION_KEEP_CEC_ENABLED_IN_OFFLINE_MODE =
+ "persist.sys.hdmi.property_user_action_keep_cec_enabled_in_offline_mode";
static final int RECORDING_TYPE_DIGITAL_RF = 1;
static final int RECORDING_TYPE_ANALOGUE_RF = 2;
static final int RECORDING_TYPE_EXTERNAL_PHYSICAL_ADDRESS = 3;
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 0c5069f81774..6e98bff8dda5 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -4027,7 +4027,8 @@ public class HdmiControlService extends SystemService {
return;
}
if (isTvDevice() && getDisableCecOnStandbyByLowEnergyMode()
- && mPowerManager.isLowPowerStandbyEnabled()) {
+ && mPowerManager.isLowPowerStandbyEnabled()
+ && !userEnabledCecInOfflineMode()) {
Slog.w(TAG, "Disable CEC on standby due to low power energy mode.");
setWasCecDisabledOnStandbyByLowEnergyMode(true);
getHdmiCecConfig().setIntValue(
@@ -5225,4 +5226,14 @@ public class HdmiControlService extends SystemService {
Constants.PROPERTY_WAS_CEC_DISABLED_ON_STANDBY_BY_LOW_ENERGY_MODE,
String.valueOf(value));
}
+
+ /**
+ * Reads the property that checks if CEC was enabled by the user while in offline mode such that
+ * it won't be disabled when going to sleep by low energy mode.
+ */
+ @VisibleForTesting
+ protected boolean userEnabledCecInOfflineMode() {
+ return SystemProperties.getBoolean(
+ Constants.PROPERTY_USER_ACTION_KEEP_CEC_ENABLED_IN_OFFLINE_MODE, false);
+ }
}
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index 76049ca824c2..d177d0e5f2eb 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -34,6 +34,7 @@ import android.hardware.contexthub.ErrorCode;
import android.hardware.contexthub.HubEndpointInfo;
import android.hardware.contexthub.IContextHubEndpoint;
import android.hardware.contexthub.IContextHubEndpointCallback;
+import android.hardware.contexthub.IContextHubEndpointDiscoveryCallback;
import android.hardware.contexthub.MessageDeliveryStatus;
import android.hardware.location.ContextHubInfo;
import android.hardware.location.ContextHubMessage;
@@ -793,6 +794,31 @@ public class ContextHubService extends IContextHubService.Stub {
return null;
}
+ @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
+ @Override
+ public void registerEndpointDiscoveryCallbackId(
+ long endpointId, IContextHubEndpointDiscoveryCallback callback) throws RemoteException {
+ super.registerEndpointDiscoveryCallbackId_enforcePermission();
+ // TODO(b/375487784): Implement this
+ }
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
+ @Override
+ public void registerEndpointDiscoveryCallbackDescriptor(
+ String serviceDescriptor, IContextHubEndpointDiscoveryCallback callback)
+ throws RemoteException {
+ super.registerEndpointDiscoveryCallbackDescriptor_enforcePermission();
+ // TODO(b/375487784): Implement this
+ }
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
+ @Override
+ public void unregisterEndpointDiscoveryCallback(IContextHubEndpointDiscoveryCallback callback)
+ throws RemoteException {
+ super.unregisterEndpointDiscoveryCallback_enforcePermission();
+ // TODO(b/375487784): Implement this
+ }
+
/**
* Creates an internal load transaction callback to be used for old API clients
*
diff --git a/services/core/java/com/android/server/pm/InstallDependencyHelper.java b/services/core/java/com/android/server/pm/InstallDependencyHelper.java
index 5bd2c994160c..42b8dd71c6e2 100644
--- a/services/core/java/com/android/server/pm/InstallDependencyHelper.java
+++ b/services/core/java/com/android/server/pm/InstallDependencyHelper.java
@@ -21,6 +21,7 @@ import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SHARED_LI
import static android.os.Process.SYSTEM_UID;
import android.annotation.NonNull;
+import android.app.role.RoleManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -35,6 +36,7 @@ import android.os.Handler;
import android.os.OutcomeReceiver;
import android.os.Process;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.util.ArraySet;
import android.util.Slog;
@@ -52,6 +54,8 @@ import java.util.concurrent.TimeUnit;
public class InstallDependencyHelper {
private static final String TAG = InstallDependencyHelper.class.getSimpleName();
private static final boolean DEBUG = true;
+ private static final String ROLE_SYSTEM_DEPENDENCY_INSTALLER =
+ "android.app.role.SYSTEM_DEPENDENCY_INSTALLER";
// The maximum amount of time to wait before the system unbinds from the verifier.
private static final long UNBIND_TIMEOUT_MILLIS = TimeUnit.HOURS.toMillis(6);
private static final long REQUEST_TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(1);
@@ -154,8 +158,20 @@ public class InstallDependencyHelper {
}
}
+ RoleManager roleManager = mContext.getSystemService(RoleManager.class);
+ if (roleManager == null) {
+ Slog.w(TAG, "Cannot find RoleManager system service");
+ return false;
+ }
+ List<String> holders = roleManager.getRoleHoldersAsUser(
+ ROLE_SYSTEM_DEPENDENCY_INSTALLER, UserHandle.of(userId));
+ if (holders.isEmpty()) {
+ Slog.w(TAG, "No holders of ROLE_SYSTEM_DEPENDENCY_INSTALLER found");
+ return false;
+ }
+
Intent serviceIntent = new Intent(ACTION_INSTALL_DEPENDENCY);
- // TODO(b/372862145): Use RoleManager to find the package name
+ serviceIntent.setPackage(holders.getFirst());
List<ResolveInfo> resolvedIntents = snapshot.queryIntentServicesInternal(
serviceIntent, /*resolvedType=*/ null, /*flags=*/0,
userId, SYSTEM_UID, Process.INVALID_PID,
@@ -165,7 +181,6 @@ public class InstallDependencyHelper {
return false;
}
-
ResolveInfo resolveInfo = resolvedIntents.getFirst();
ComponentName componentName = resolveInfo.getComponentInfo().getComponentName();
serviceIntent.setComponent(componentName);
diff --git a/services/core/java/com/android/server/pm/PackageAbiHelper.java b/services/core/java/com/android/server/pm/PackageAbiHelper.java
index c66a9e98c1d3..09302996d228 100644
--- a/services/core/java/com/android/server/pm/PackageAbiHelper.java
+++ b/services/core/java/com/android/server/pm/PackageAbiHelper.java
@@ -18,6 +18,7 @@ package com.android.server.pm;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.pm.ApplicationInfo;
import android.util.ArraySet;
import android.util.Pair;
@@ -28,8 +29,6 @@ import com.android.server.pm.pkg.PackageStateInternal;
import java.io.File;
-
-
// TODO: Move to .parsing sub-package
@VisibleForTesting
public interface PackageAbiHelper {
@@ -79,6 +78,23 @@ public interface PackageAbiHelper {
AndroidPackage scannedPackage);
/**
+ * Checks alignment of APK and native libraries for 16KB device
+ *
+ * @param pkg AndroidPackage for which alignment check is being done
+ * @param libraryRoot directory for libraries
+ * @param nativeLibraryRootRequiresIsa use isa
+ * @param cpuAbiOverride ABI override mentioned in package
+ * @return {ApplicationInfo.PageSizeAppCompat} if successful or error code
+ * which suggests undefined mode
+ */
+ @ApplicationInfo.PageSizeAppCompatFlags
+ int checkPackageAlignment(
+ AndroidPackage pkg,
+ String libraryRoot,
+ boolean nativeLibraryRootRequiresIsa,
+ String cpuAbiOverride);
+
+ /**
* The native library paths and related properties that should be set on a
* {@link ParsedPackage}.
*/
diff --git a/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java b/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java
index 9db4d33106ce..0f245b6c2201 100644
--- a/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java
+++ b/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java
@@ -625,4 +625,20 @@ final class PackageAbiHelperImpl implements PackageAbiHelper {
}
return adjustedAbi;
}
+
+ @Override
+ public int checkPackageAlignment(
+ AndroidPackage pkg,
+ String libraryRoot,
+ boolean nativeLibraryRootRequiresIsa,
+ String abiOverride) {
+ NativeLibraryHelper.Handle handle = null;
+ try {
+ handle = AndroidPackageUtils.createNativeLibraryHandle(pkg);
+ return NativeLibraryHelper.checkAlignmentForCompatMode(
+ handle, libraryRoot, nativeLibraryRootRequiresIsa, abiOverride);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 715633410575..040b1943b23d 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -5875,6 +5875,67 @@ public class PackageManagerService implements PackageSender, TestUtilityService
userId, callingPackage);
}
+ @Override
+ public void setPageSizeAppCompatFlagsSettingsOverride(String packageName, boolean enabled) {
+ final int callingUid = Binder.getCallingUid();
+ final int callingAppId = UserHandle.getAppId(callingUid);
+
+ if (!PackageManagerServiceUtils.isSystemOrRoot(callingAppId)) {
+ throw new SecurityException("Caller must be the system or root.");
+ }
+
+ int settingsMode = enabled
+ ? ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_SETTINGS_OVERRIDE_ENABLED
+ : ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_SETTINGS_OVERRIDE_DISABLED;
+ PackageStateMutator.Result result =
+ commitPackageStateMutation(
+ null,
+ packageName,
+ packageState ->
+ packageState
+ .setPageSizeAppCompatFlags(settingsMode));
+ if (result.isSpecificPackageNull()) {
+ throw new IllegalArgumentException("Unknown package: " + packageName);
+ }
+ scheduleWriteSettings();
+ }
+
+ @Override
+ public boolean isPageSizeCompatEnabled(String packageName) {
+ final int callingUid = Binder.getCallingUid();
+ final int callingAppId = UserHandle.getAppId(callingUid);
+ final int userId = UserHandle.getCallingUserId();
+
+ if (!PackageManagerServiceUtils.isSystemOrRoot(callingAppId)) {
+ throw new SecurityException("Caller must be the system or root.");
+ }
+
+ PackageStateInternal packageState =
+ snapshotComputer().getPackageStateForInstalledAndFiltered(
+ packageName, callingUid, userId);
+
+ return packageState == null ? false : packageState.isPageSizeAppCompatEnabled();
+ }
+
+ @Override
+ public String getPageSizeCompatWarningMessage(String packageName) {
+ final int callingUid = Binder.getCallingUid();
+ final int callingAppId = UserHandle.getAppId(callingUid);
+ final int userId = UserHandle.getCallingUserId();
+
+ if (!PackageManagerServiceUtils.isSystemOrRoot(callingAppId)) {
+ throw new SecurityException("Caller must be the system or root.");
+ }
+
+ PackageStateInternal packageState =
+ snapshotComputer().getPackageStateForInstalledAndFiltered(
+ packageName, callingUid, userId);
+
+ return packageState == null
+ ? null
+ : packageState.getPageSizeCompatWarningMessage(mContext);
+ }
+
@android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_USERS)
@Override
public boolean setApplicationHiddenSettingAsUser(String packageName, boolean hidden,
@@ -6579,6 +6640,20 @@ public class PackageManagerService implements PackageSender, TestUtilityService
}
@Override
+ @NonNull
+ public List<String> getAllApexDirectories() {
+ PackageManagerServiceUtils.enforceSystemOrRoot(
+ "getAllApexDirectories can only be called by system or root");
+ List<String> apexDirectories = new ArrayList<>();
+ List<ApexManager.ActiveApexInfo> apexes = mApexManager.getActiveApexInfos();
+ for (int i = 0; i < apexes.size(); i++) {
+ ApexManager.ActiveApexInfo apex = apexes.get(i);
+ apexDirectories.add(apex.apexDirectory.getAbsolutePath());
+ }
+ return apexDirectories;
+ }
+
+ @Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
throws RemoteException {
try {
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 9428de700385..fb16b862b275 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -27,6 +27,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.ComponentName;
+import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.SharedLibraryInfo;
@@ -221,6 +222,8 @@ public class PackageSetting extends SettingBase implements PackageStateInternal
/** @see PackageState#getCategoryOverride() */
private int categoryOverride = ApplicationInfo.CATEGORY_UNDEFINED;
+ private int mPageSizeAppCompatFlags = ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED;
+
@NonNull
private final PackageStateUnserialized pkgState = new PackageStateUnserialized(this);
@@ -863,6 +866,8 @@ public class PackageSetting extends SettingBase implements PackageStateInternal
}
copyMimeGroups(other.mimeGroups);
+ mPageSizeAppCompatFlags = other.mPageSizeAppCompatFlags;
+
pkgState.updateFrom(other.pkgState);
onChanged();
}
@@ -1617,6 +1622,34 @@ public class PackageSetting extends SettingBase implements PackageStateInternal
return this;
}
+ /**
+ * @see Set page size app compat mode.
+ */
+ public PackageSetting setPageSizeAppCompatFlags(int mode) {
+ if (mode < 0 || mode >= ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_MAX) {
+ throw new IllegalArgumentException("Invalid page size compat mode specified");
+ }
+
+ // OR assignment is used here to avoid overriding the mode set by the manifest.
+ this.mPageSizeAppCompatFlags |= mode;
+
+ // Only one bit of the following can be set at same time. Both are needed to detect app
+ // compat 'disabled' state from settings vs bit was never set.
+ if (ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_SETTINGS_OVERRIDE_ENABLED == mode) {
+ this.mPageSizeAppCompatFlags &=
+ ~ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_SETTINGS_OVERRIDE_DISABLED;
+ } else if (ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_SETTINGS_OVERRIDE_DISABLED == mode) {
+ this.mPageSizeAppCompatFlags &=
+ ~ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_SETTINGS_OVERRIDE_ENABLED;
+ }
+ onChanged();
+ return this;
+ }
+
+ public int getPageSizeAppCompatFlags() {
+ return mPageSizeAppCompatFlags;
+ }
+
public PackageSetting setLegacyNativeLibraryPath(
String legacyNativeLibraryPathString) {
this.legacyNativeLibraryPath = legacyNativeLibraryPathString;
@@ -1787,6 +1820,63 @@ public class PackageSetting extends SettingBase implements PackageStateInternal
return getBoolean(Booleans.SCANNED_AS_STOPPED_SYSTEM_APP);
}
+ /** Returns true if ELF files will be loaded in Page size compatibility mode */
+ @Override
+ public boolean isPageSizeAppCompatEnabled() {
+ // If manifest or settings has disabled the compat mode, don't run app in compat mode.
+ boolean manifestOverrideDisabled = (mPageSizeAppCompatFlags
+ & ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_MANIFEST_OVERRIDE_DISABLED) != 0;
+ boolean settingsOverrideDisabled = (mPageSizeAppCompatFlags
+ & ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_SETTINGS_OVERRIDE_DISABLED) != 0;
+ if (manifestOverrideDisabled || settingsOverrideDisabled) {
+ return false;
+ }
+
+ int mask =
+ ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_ELF_NOT_ALIGNED
+ | ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_MANIFEST_OVERRIDE_ENABLED
+ | ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_SETTINGS_OVERRIDE_ENABLED;
+ return (mPageSizeAppCompatFlags & mask) != 0;
+ }
+
+ /**
+ * Returns dialog string based on alignment of uncompressed shared libs inside the APK and ELF
+ * alignment.
+ */
+ @Override
+ public String getPageSizeCompatWarningMessage(Context context) {
+ boolean manifestOverrideEnabled = (mPageSizeAppCompatFlags
+ & ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_MANIFEST_OVERRIDE_ENABLED) != 0;
+ boolean settingsOverrideEnabled = (mPageSizeAppCompatFlags
+ & ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_MANIFEST_OVERRIDE_ENABLED) != 0;
+ if (manifestOverrideEnabled || settingsOverrideEnabled) {
+ return null;
+ }
+
+ boolean uncompressedLibsNotAligned = (mPageSizeAppCompatFlags
+ & ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_UNCOMPRESSED_LIBS_NOT_ALIGNED) != 0;
+ boolean elfNotAligned = (mPageSizeAppCompatFlags
+ & ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_ELF_NOT_ALIGNED) != 0;
+
+ if (uncompressedLibsNotAligned && elfNotAligned) {
+ return context.getText(
+ com.android.internal.R.string.page_size_compat_apk_and_elf_warning)
+ .toString();
+ }
+
+ if (uncompressedLibsNotAligned) {
+ return context.getText(com.android.internal.R.string.page_size_compat_apk_warning)
+ .toString();
+ }
+
+ if (elfNotAligned) {
+ return context.getText(com.android.internal.R.string.page_size_compat_elf_warning)
+ .toString();
+ }
+
+ return null;
+ }
+
// Code below generated by codegen v1.0.23.
@@ -1952,7 +2042,6 @@ public class PackageSetting extends SettingBase implements PackageStateInternal
@Deprecated
private void __metadata() {}
-
//@formatter:on
// End of generated code
diff --git a/services/core/java/com/android/server/pm/SaferIntentUtils.java b/services/core/java/com/android/server/pm/SaferIntentUtils.java
index bc36fabc7f67..854e142c7c2f 100644
--- a/services/core/java/com/android/server/pm/SaferIntentUtils.java
+++ b/services/core/java/com/android/server/pm/SaferIntentUtils.java
@@ -26,6 +26,7 @@ import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.compat.annotation.ChangeId;
+import android.compat.annotation.Disabled;
import android.compat.annotation.EnabledAfter;
import android.compat.annotation.Overridable;
import android.content.Intent;
@@ -88,6 +89,22 @@ public class SaferIntentUtils {
@EnabledAfter(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
private static final long IMPLICIT_INTENTS_ONLY_MATCH_EXPORTED_COMPONENTS = 229362273;
+ /**
+ * Intents sent from apps enabling this feature will stop resolving to components with
+ * non matching intent filters, even when explicitly setting a component name, unless the
+ * target components are in the same app as the calling app.
+ * <p>
+ * When an app registers an exported component in its manifest and adds &lt;intent-filter&gt;s,
+ * the component can be started by any intent - even those that do not match the intent filter.
+ * This has proven to be something that many developers find counterintuitive.
+ * Without checking the intent when the component is started, in some circumstances this can
+ * allow 3P apps to trigger internal-only functionality.
+ */
+ @ChangeId
+ @Overridable
+ @Disabled
+ private static final long ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS = 161252188;
+
@Nullable
private static ParsedMainComponent infoToComponent(
ComponentInfo info, ComponentResolverApi resolver, boolean isReceiver) {
@@ -249,6 +266,20 @@ public class SaferIntentUtils {
*/
public static void enforceIntentFilterMatching(
IntentArgs args, List<ResolveInfo> resolveInfos) {
+ // Switch to the new intent matching logic if the feature flag is enabled.
+ // Otherwise, use the existing AppCompat based implementation.
+ if (Flags.enableIntentMatchingFlags()) {
+ enforceIntentFilterMatchingWithIntentMatchingFlags(args, resolveInfos);
+ } else {
+ enforceIntentFilterMatchingWithAppCompat(args, resolveInfos);
+ }
+ }
+
+ /**
+ * This version of the method is implemented in Android B and uses "IntentMatchingFlags"
+ */
+ private static void enforceIntentFilterMatchingWithIntentMatchingFlags(
+ IntentArgs args, List<ResolveInfo> resolveInfos) {
if (DISABLE_ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS.get()) return;
// Do not enforce filter matching when the caller is system or root
@@ -339,6 +370,97 @@ public class SaferIntentUtils {
}
/**
+ * This version of the method is implemented in Android V and uses "AppCompat"
+ */
+ private static void enforceIntentFilterMatchingWithAppCompat(
+ IntentArgs args, List<ResolveInfo> resolveInfos) {
+ if (DISABLE_ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS.get()) return;
+
+ // Do not enforce filter matching when the caller is system or root
+ if (ActivityManager.canAccessUnexportedComponents(args.callingUid)) return;
+
+ final Computer computer = (Computer) args.snapshot;
+ final ComponentResolverApi resolver = computer.getComponentResolver();
+
+ final Printer logPrinter = DEBUG_INTENT_MATCHING
+ ? new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM)
+ : null;
+
+ final boolean enforceMatch = Flags.enforceIntentFilterMatch()
+ && args.isChangeEnabled(ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS);
+ final boolean blockNullAction = Flags.blockNullActionIntents()
+ && args.isChangeEnabled(IntentFilter.BLOCK_NULL_ACTION_INTENTS);
+
+ for (int i = resolveInfos.size() - 1; i >= 0; --i) {
+ final ComponentInfo info = resolveInfos.get(i).getComponentInfo();
+
+ // Skip filter matching when the caller is targeting the same app
+ if (UserHandle.isSameApp(args.callingUid, info.applicationInfo.uid)) {
+ continue;
+ }
+
+ final ParsedMainComponent comp = infoToComponent(info, resolver, args.isReceiver);
+
+ if (comp == null || comp.getIntents().isEmpty()) {
+ continue;
+ }
+
+ Boolean match = null;
+
+ if (args.intent.getAction() == null) {
+ args.reportEvent(
+ UNSAFE_INTENT_EVENT_REPORTED__EVENT_TYPE__NULL_ACTION_MATCH,
+ enforceMatch && blockNullAction);
+ if (blockNullAction) {
+ // Skip intent filter matching if blocking null action
+ match = false;
+ }
+ }
+
+ if (match == null) {
+ // Check if any intent filter matches
+ for (int j = 0, size = comp.getIntents().size(); j < size; ++j) {
+ IntentFilter intentFilter = comp.getIntents().get(j).getIntentFilter();
+ if (IntentResolver.intentMatchesFilter(
+ intentFilter, args.intent, args.resolvedType)) {
+ match = true;
+ break;
+ }
+ }
+ }
+
+ // At this point, the value `match` has the following states:
+ // null : Intent does not match any intent filter
+ // false: Null action intent detected AND blockNullAction == true
+ // true : The intent matches at least one intent filter
+
+ if (match == null) {
+ args.reportEvent(
+ UNSAFE_INTENT_EVENT_REPORTED__EVENT_TYPE__EXPLICIT_INTENT_FILTER_UNMATCH,
+ enforceMatch);
+ match = false;
+ }
+
+ if (!match) {
+ // All non-matching intents has to be marked accordingly
+ if (Flags.enforceIntentFilterMatch()) {
+ args.intent.addExtendedFlags(Intent.EXTENDED_FLAG_FILTER_MISMATCH);
+ }
+ if (enforceMatch) {
+ Slog.w(TAG, "Intent does not match component's intent filter: " + args.intent);
+ Slog.w(TAG, "Access blocked: " + comp.getComponentName());
+ if (DEBUG_INTENT_MATCHING) {
+ Slog.v(TAG, "Component intent filters:");
+ comp.getIntents().forEach(f -> f.getIntentFilter().dump(logPrinter, " "));
+ Slog.v(TAG, "-----------------------------");
+ }
+ resolveInfos.remove(i);
+ }
+ }
+ }
+ }
+
+ /**
* Filter non-exported components from the componentList if intent is implicit.
* <p>
* Implicit intents cannot be used to start Services since API 21+.
diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java
index 0802e9ee50bc..2665196bf3be 100644
--- a/services/core/java/com/android/server/pm/ScanPackageUtils.java
+++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java
@@ -52,6 +52,7 @@ import static com.android.server.pm.PackageManagerServiceUtils.getLastModifiedTi
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.pm.ApplicationInfo;
+import android.content.pm.Flags;
import android.content.pm.PackageManager;
import android.content.pm.SharedLibraryInfo;
import android.content.pm.SigningDetails;
@@ -63,6 +64,8 @@ import android.os.Process;
import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
+import android.system.Os;
+import android.system.OsConstants;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -105,6 +108,9 @@ import java.util.UUID;
* Helper class that handles package scanning logic
*/
final class ScanPackageUtils {
+
+ public static final int PAGE_SIZE_16KB = 16384;
+
/**
* Just scans the package without any side effects.
*
@@ -418,6 +424,32 @@ final class ScanPackageUtils {
+ " abiOverride=" + pkgSetting.getCpuAbiOverride());
}
+ boolean is16KbDevice = Os.sysconf(OsConstants._SC_PAGESIZE) == PAGE_SIZE_16KB;
+ if (Flags.appCompatOption16kb() && is16KbDevice) {
+ // Alignment checks are used decide whether this app should run in compat mode when
+ // nothing was specified in manifest. Manifest should always take precedence over
+ // something decided by platform.
+ if (parsedPackage.getPageSizeAppCompatFlags()
+ > ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED) {
+ pkgSetting.setPageSizeAppCompatFlags(parsedPackage.getPageSizeAppCompatFlags());
+ } else {
+ // 16 KB is only support for 64 bit ABIs and for apps which are being installed
+ // Check alignment. System, Apex and Platform packages should be page-agnostic now
+ if ((Build.SUPPORTED_64_BIT_ABIS.length > 0)
+ && !isSystemApp
+ && !isApex
+ && !isPlatformPackage) {
+ int mode =
+ packageAbiHelper.checkPackageAlignment(
+ parsedPackage,
+ pkgSetting.getLegacyNativeLibraryPath(),
+ parsedPackage.isNativeLibraryRootRequiresIsa(),
+ pkgSetting.getCpuAbiOverride());
+ pkgSetting.setPageSizeAppCompatFlags(mode);
+ }
+ }
+ }
+
if ((scanFlags & SCAN_BOOTING) == 0 && oldSharedUserSetting != null) {
// We don't do this here during boot because we can do it all
// at once after scanning all existing packages.
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 1f672a093b38..485a28070bc5 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -3313,6 +3313,11 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
if (pkg.getBaseRevisionCode() != 0) {
serializer.attributeInt(null, "baseRevisionCode", pkg.getBaseRevisionCode());
}
+ if (pkg.getPageSizeAppCompatFlags()
+ != ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED) {
+ serializer.attributeInt(null, "pageSizeCompat", pkg.getPageSizeAppCompatFlags());
+ }
+
serializer.attributeFloat(null, "loadingProgress", pkg.getLoadingProgress());
serializer.attributeLongHex(null, "loadingCompletedTime", pkg.getLoadingCompletedTime());
@@ -4129,6 +4134,7 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
boolean isScannedAsStoppedSystemApp = false;
boolean isSdkLibrary = false;
int baseRevisionCode = 0;
+ int PageSizeCompat = 0;
try {
name = parser.getAttributeValue(null, ATTR_NAME);
realName = parser.getAttributeValue(null, "realName");
@@ -4175,6 +4181,8 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
appMetadataSource = parser.getAttributeInt(null, "appMetadataSource",
PackageManager.APP_METADATA_SOURCE_UNKNOWN);
baseRevisionCode = parser.getAttributeInt(null, "baseRevisionCode", 0);
+ PageSizeCompat = parser.getAttributeInt(null, "pageSizeCompat",
+ ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED);
isScannedAsStoppedSystemApp = parser.getAttributeBoolean(null,
"scannedAsStoppedSystemApp", false);
@@ -4330,7 +4338,8 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
.setTargetSdkVersion(targetSdkVersion)
.setBaseRevisionCode(baseRevisionCode)
.setRestrictUpdateHash(restrictUpdateHash)
- .setScannedAsStoppedSystemApp(isScannedAsStoppedSystemApp);
+ .setScannedAsStoppedSystemApp(isScannedAsStoppedSystemApp)
+ .setPageSizeAppCompatFlags(PageSizeCompat);
// Handle legacy string here for single-user mode
final String enabledStr = parser.getAttributeValue(null, ATTR_ENABLED);
if (enabledStr != null) {
@@ -5211,6 +5220,10 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
pw.print(" (override=true)");
}
pw.println();
+ pw.print(prefix);
+ pw.print(" pageSizeCompat=");
+ pw.print(ps.getPageSizeAppCompatFlags());
+ pw.println();
if (!ps.getPkg().getQueriesPackages().isEmpty()) {
pw.append(prefix).append(" queriesPackages=")
.println(ps.getPkg().getQueriesPackages());
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index b2b8aaf7dd2a..066fce068d61 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -6122,8 +6122,11 @@ public class UserManagerService extends IUserManager.Stub {
// If the user switch hasn't been explicitly toggled on or off by the user, turn it on.
if (android.provider.Settings.Global.getString(mContext.getContentResolver(),
android.provider.Settings.Global.USER_SWITCHER_ENABLED) == null) {
- android.provider.Settings.Global.putInt(mContext.getContentResolver(),
- android.provider.Settings.Global.USER_SWITCHER_ENABLED, 1);
+ if (Resources.getSystem().getBoolean(
+ com.android.internal.R.bool.config_enableUserSwitcherUponUserCreation)) {
+ android.provider.Settings.Global.putInt(mContext.getContentResolver(),
+ android.provider.Settings.Global.USER_SWITCHER_ENABLED, 1);
+ }
}
}
}
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 bbc17c83cfac..33fc066a62ee 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageState.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageState.java
@@ -22,6 +22,7 @@ import android.annotation.Nullable;
import android.annotation.Size;
import android.annotation.SystemApi;
import android.annotation.UserIdInt;
+import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
@@ -198,6 +199,21 @@ public interface PackageState {
int getCategoryOverride();
/**
+ * Returns true if ELF files will be loaded in Page size compatibility mode
+ *
+ * @hide
+ */
+ boolean isPageSizeAppCompatEnabled();
+
+ /**
+ * Returns dialog string based on alignment of uncompressed shared libs inside the APK and ELF
+ * alignment.
+ *
+ * @hide
+ */
+ String getPageSizeCompatWarningMessage(Context context);
+
+ /**
* The install time CPU override, if any. This value is written at install time
* and doesn't change during the life of an install. If non-null,
* {@link #getPrimaryCpuAbiLegacy()} will also contain the same value.
diff --git a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java
index 253eb4006122..a46c4a695d60 100644
--- a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java
+++ b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java
@@ -257,6 +257,16 @@ public class PackageStateMutator {
@NonNull
@Override
+ public PackageStateWrite setPageSizeAppCompatFlags(
+ @ApplicationInfo.PageSizeAppCompatFlags int mode) {
+ if (mState != null) {
+ mState.setPageSizeAppCompatFlags(mode);
+ }
+ return this;
+ }
+
+ @NonNull
+ @Override
public PackageStateWrite setUpdateAvailable(boolean updateAvailable) {
if (mState != null) {
mState.setUpdateAvailable(updateAvailable);
diff --git a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateWrite.java b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateWrite.java
index 55d96f3aee08..f8f8695b2832 100644
--- a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateWrite.java
+++ b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateWrite.java
@@ -46,6 +46,10 @@ public interface PackageStateWrite {
@NonNull
PackageStateWrite setCategoryOverride(@ApplicationInfo.Category int category);
+ /** set 16Kb App compat mode. @see ApplicationInfo.PageSizeAppCompatFlags */
+ @NonNull
+ PackageStateWrite setPageSizeAppCompatFlags(@ApplicationInfo.PageSizeAppCompatFlags int mode);
+
@NonNull
PackageStateWrite setUpdateAvailable(boolean updateAvailable);
diff --git a/services/core/java/com/android/server/power/hint/HintManagerService.java b/services/core/java/com/android/server/power/hint/HintManagerService.java
index 71f67d82dbec..aba15c83f907 100644
--- a/services/core/java/com/android/server/power/hint/HintManagerService.java
+++ b/services/core/java/com/android/server/power/hint/HintManagerService.java
@@ -34,7 +34,9 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.hardware.power.ChannelConfig;
import android.hardware.power.CpuHeadroomParams;
+import android.hardware.power.CpuHeadroomResult;
import android.hardware.power.GpuHeadroomParams;
+import android.hardware.power.GpuHeadroomResult;
import android.hardware.power.IPower;
import android.hardware.power.SessionConfig;
import android.hardware.power.SessionTag;
@@ -79,6 +81,7 @@ import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
@@ -96,10 +99,10 @@ public final class HintManagerService extends SystemService {
private static final int EVENT_CLEAN_UP_UID = 3;
@VisibleForTesting static final int CLEAN_UP_UID_DELAY_MILLIS = 1000;
+ // The minimum interval between the headroom calls as rate limiting.
private static final int DEFAULT_GPU_HEADROOM_INTERVAL_MILLIS = 1000;
private static final int DEFAULT_CPU_HEADROOM_INTERVAL_MILLIS = 1000;
private static final int HEADROOM_INTERVAL_UNSUPPORTED = -1;
- @VisibleForTesting static final int DEFAULT_HEADROOM_PID = -1;
@VisibleForTesting final long mHintSessionPreferredRate;
@@ -184,73 +187,77 @@ public final class HintManagerService extends SystemService {
private static final String PROPERTY_SF_ENABLE_CPU_HINT = "debug.sf.enable_adpf_cpu_hint";
private static final String PROPERTY_HWUI_ENABLE_HINT_MANAGER = "debug.hwui.use_hint_manager";
private static final String PROPERTY_USE_HAL_HEADROOMS = "persist.hms.use_hal_headrooms";
+ private static final String PROPERTY_CHECK_HEADROOM_TID = "persist.hms.check_headroom_tid";
private Boolean mFMQUsesIntegratedEventFlag = false;
private final Object mCpuHeadroomLock = new Object();
- private static class CpuHeadroomCacheItem {
- long mExpiredTimeMillis;
- CpuHeadroomParamsInternal mParams;
- float[] mHeadroom;
- long mPid;
- CpuHeadroomCacheItem(long expiredTimeMillis, CpuHeadroomParamsInternal params,
- float[] headroom, long pid) {
- mExpiredTimeMillis = expiredTimeMillis;
- mParams = params;
- mPid = pid;
- mHeadroom = headroom;
- }
+ // this cache tracks the expiration time of the items and performs cleanup on lookup
+ private static class HeadroomCache<K, V> {
+ final List<HeadroomCacheItem> mItemList;
+ final Map<K, HeadroomCacheItem> mKeyItemMap;
+ final long mItemExpDurationMillis;
+
+ class HeadroomCacheItem {
+ long mExpTime;
+ K mKey;
+ V mValue;
- private boolean match(CpuHeadroomParamsInternal params, long pid) {
- if (mParams == null && params == null) return true;
- if (mParams != null) {
- return mParams.equals(params) && pid == mPid;
+ HeadroomCacheItem(K k, V v) {
+ mExpTime = System.currentTimeMillis() + mItemExpDurationMillis;
+ mKey = k;
+ mValue = v;
}
- return false;
- }
- private boolean isExpired() {
- return System.currentTimeMillis() > mExpiredTimeMillis;
+ boolean isExpired() {
+ return mExpTime <= System.currentTimeMillis();
+ }
}
- }
-
- @GuardedBy("mCpuHeadroomLock")
- private final List<CpuHeadroomCacheItem> mCpuHeadroomCache;
- private final long mCpuHeadroomIntervalMillis;
-
- private final Object mGpuHeadroomLock = new Object();
- private static class GpuHeadroomCacheItem {
- long mExpiredTimeMillis;
- GpuHeadroomParamsInternal mParams;
- float mHeadroom;
-
- GpuHeadroomCacheItem(long expiredTimeMillis, GpuHeadroomParamsInternal params,
- float headroom) {
- mExpiredTimeMillis = expiredTimeMillis;
- mParams = params;
- mHeadroom = headroom;
+ void add(K key, V value) {
+ if (mKeyItemMap.containsKey(key)) {
+ final HeadroomCacheItem item = mKeyItemMap.get(key);
+ mItemList.remove(item);
+ }
+ final HeadroomCacheItem item = new HeadroomCacheItem(key, value);
+ mItemList.add(item);
+ mKeyItemMap.put(key, item);
}
- private boolean match(GpuHeadroomParamsInternal params) {
- if (mParams == null && params == null) return true;
- if (mParams != null) {
- return mParams.equals(params);
+ V get(K key) {
+ while (!mItemList.isEmpty() && mItemList.getFirst().isExpired()) {
+ mKeyItemMap.remove(mItemList.removeFirst().mKey);
}
- return false;
+ final HeadroomCacheItem item = mKeyItemMap.get(key);
+ if (item == null) {
+ return null;
+ }
+ return item.mValue;
}
- private boolean isExpired() {
- return System.currentTimeMillis() > mExpiredTimeMillis;
+ HeadroomCache(int size, long expDurationMillis) {
+ mItemList = new LinkedList<>();
+ mKeyItemMap = new ArrayMap<>(size);
+ mItemExpDurationMillis = expDurationMillis;
}
}
+ @GuardedBy("mCpuHeadroomLock")
+ private final HeadroomCache<CpuHeadroomParams, CpuHeadroomResult> mCpuHeadroomCache;
+ private final long mCpuHeadroomIntervalMillis;
+
+ private final Object mGpuHeadroomLock = new Object();
+
@GuardedBy("mGpuHeadroomLock")
- private final List<GpuHeadroomCacheItem> mGpuHeadroomCache;
+ private final HeadroomCache<GpuHeadroomParams, GpuHeadroomResult> mGpuHeadroomCache;
private final long mGpuHeadroomIntervalMillis;
+ // these are set to default values in CpuHeadroomParamsInternal and GpuHeadroomParamsInternal
+ private final int mDefaultCpuHeadroomCalculationWindowMillis;
+ private final int mDefaultGpuHeadroomCalculationWindowMillis;
+
@VisibleForTesting
final IHintManager.Stub mService = new BinderService();
@@ -303,26 +310,31 @@ public final class HintManagerService extends SystemService {
}
}
mCpuHeadroomIntervalMillis = cpuHeadroomIntervalMillis;
+ mDefaultCpuHeadroomCalculationWindowMillis =
+ new CpuHeadroomParamsInternal().calculationWindowMillis;
+ mDefaultGpuHeadroomCalculationWindowMillis =
+ new GpuHeadroomParamsInternal().calculationWindowMillis;
mGpuHeadroomIntervalMillis = gpuHeadroomIntervalMillis;
if (mCpuHeadroomIntervalMillis > 0) {
- mCpuHeadroomCache = new ArrayList<>(4);
+ mCpuHeadroomCache = new HeadroomCache<>(2, mCpuHeadroomIntervalMillis);
} else {
mCpuHeadroomCache = null;
}
if (mGpuHeadroomIntervalMillis > 0) {
- mGpuHeadroomCache = new ArrayList<>(2);
+ mGpuHeadroomCache = new HeadroomCache<>(2, mGpuHeadroomIntervalMillis);
} else {
mGpuHeadroomCache = null;
}
}
private long checkCpuHeadroomSupport() {
+ final CpuHeadroomParams params = new CpuHeadroomParams();
+ params.tids = new int[]{Process.myPid()};
try {
synchronized (mCpuHeadroomLock) {
- final CpuHeadroomParams defaultParams = new CpuHeadroomParams();
- defaultParams.pid = Process.myPid();
- float[] ret = mPowerHal.getCpuHeadroom(defaultParams);
- if (ret != null && ret.length > 0) {
+ final CpuHeadroomResult ret = mPowerHal.getCpuHeadroom(params);
+ if (ret != null && ret.getTag() == CpuHeadroomResult.globalHeadroom
+ && !Float.isNaN(ret.getGlobalHeadroom())) {
return Math.max(
DEFAULT_CPU_HEADROOM_INTERVAL_MILLIS,
mPowerHal.getCpuHeadroomMinIntervalMillis());
@@ -330,27 +342,29 @@ public final class HintManagerService extends SystemService {
}
} catch (UnsupportedOperationException e) {
- Slog.w(TAG, "getCpuHeadroom HAL API is not supported", e);
+ Slog.w(TAG, "getCpuHeadroom HAL API is not supported, params: " + params, e);
} catch (RemoteException e) {
- Slog.e(TAG, "getCpuHeadroom HAL API fails, disabling the API", e);
+ Slog.e(TAG, "getCpuHeadroom HAL API fails, disabling the API, params: " + params, e);
}
return HEADROOM_INTERVAL_UNSUPPORTED;
}
private long checkGpuHeadroomSupport() {
+ final GpuHeadroomParams params = new GpuHeadroomParams();
try {
synchronized (mGpuHeadroomLock) {
- float ret = mPowerHal.getGpuHeadroom(new GpuHeadroomParams());
- if (!Float.isNaN(ret)) {
+ final GpuHeadroomResult ret = mPowerHal.getGpuHeadroom(params);
+ if (ret != null && ret.getTag() == GpuHeadroomResult.globalHeadroom && !Float.isNaN(
+ ret.getGlobalHeadroom())) {
return Math.max(
DEFAULT_GPU_HEADROOM_INTERVAL_MILLIS,
mPowerHal.getGpuHeadroomMinIntervalMillis());
}
}
} catch (UnsupportedOperationException e) {
- Slog.w(TAG, "getGpuHeadroom HAL API is not supported", e);
+ Slog.w(TAG, "getGpuHeadroom HAL API is not supported, params: " + params, e);
} catch (RemoteException e) {
- Slog.e(TAG, "getGpuHeadroom HAL API fails, disabling the API", e);
+ Slog.e(TAG, "getGpuHeadroom HAL API fails, disabling the API, params: " + params, e);
}
return HEADROOM_INTERVAL_UNSUPPORTED;
}
@@ -1445,109 +1459,98 @@ public final class HintManagerService extends SystemService {
}
@Override
- public float[] getCpuHeadroom(@Nullable CpuHeadroomParamsInternal params) {
+ public CpuHeadroomResult getCpuHeadroom(@NonNull CpuHeadroomParamsInternal params) {
if (mCpuHeadroomIntervalMillis <= 0) {
throw new UnsupportedOperationException();
}
- CpuHeadroomParams halParams = new CpuHeadroomParams();
- halParams.pid = Binder.getCallingPid();
- if (params != null) {
- halParams.calculationType = params.calculationType;
- halParams.selectionType = params.selectionType;
- if (params.usesDeviceHeadroom) {
- halParams.pid = DEFAULT_HEADROOM_PID;
- }
- }
- synchronized (mCpuHeadroomLock) {
- while (!mCpuHeadroomCache.isEmpty()) {
- if (mCpuHeadroomCache.getFirst().isExpired()) {
- mCpuHeadroomCache.removeFirst();
- } else {
- break;
+ final CpuHeadroomParams halParams = new CpuHeadroomParams();
+ halParams.tids = new int[]{Binder.getCallingPid()};
+ halParams.calculationType = params.calculationType;
+ halParams.calculationWindowMillis = params.calculationWindowMillis;
+ halParams.selectionType = params.selectionType;
+ if (params.usesDeviceHeadroom) {
+ halParams.tids = new int[]{};
+ } else if (params.tids != null && params.tids.length > 0) {
+ if (params.tids.length > 5) {
+ throw new IllegalArgumentException(
+ "More than 5 TIDs is requested: " + params.tids.length);
+ }
+ if (SystemProperties.getBoolean(PROPERTY_CHECK_HEADROOM_TID, true)) {
+ final int tgid = Process.getThreadGroupLeader(Binder.getCallingPid());
+ for (int tid : params.tids) {
+ if (Process.getThreadGroupLeader(tid) != tgid) {
+ throw new SecurityException("TID " + tid
+ + " doesn't belong to the calling process with pid "
+ + tgid);
+ }
}
}
- for (int i = 0; i < mCpuHeadroomCache.size(); ++i) {
- final CpuHeadroomCacheItem item = mCpuHeadroomCache.get(i);
- if (item.match(params, halParams.pid)) {
- item.mExpiredTimeMillis =
- System.currentTimeMillis() + mCpuHeadroomIntervalMillis;
- mCpuHeadroomCache.remove(i);
- mCpuHeadroomCache.add(item);
- return item.mHeadroom;
- }
+ halParams.tids = params.tids;
+ }
+ if (halParams.calculationWindowMillis
+ == mDefaultCpuHeadroomCalculationWindowMillis) {
+ synchronized (mCpuHeadroomLock) {
+ final CpuHeadroomResult res = mCpuHeadroomCache.get(halParams);
+ if (res != null) return res;
}
}
// return from HAL directly
try {
- float[] headroom = mPowerHal.getCpuHeadroom(halParams);
- if (headroom == null || headroom.length == 0) {
- Slog.wtf(TAG,
- "CPU headroom from Power HAL is invalid: " + Arrays.toString(headroom));
- return new float[]{Float.NaN};
- }
- synchronized (mCpuHeadroomLock) {
- mCpuHeadroomCache.add(new CpuHeadroomCacheItem(
- System.currentTimeMillis() + mCpuHeadroomIntervalMillis,
- params, headroom, halParams.pid
- ));
+ final CpuHeadroomResult result = mPowerHal.getCpuHeadroom(halParams);
+ if (result == null) {
+ Slog.wtf(TAG, "CPU headroom from Power HAL is invalid");
+ return null;
+ }
+ if (halParams.calculationWindowMillis
+ == mDefaultCpuHeadroomCalculationWindowMillis) {
+ synchronized (mCpuHeadroomLock) {
+ mCpuHeadroomCache.add(halParams, result);
+ }
}
- return headroom;
-
+ return result;
} catch (RemoteException e) {
- return new float[]{Float.NaN};
+ Slog.e(TAG, "Failed to get CPU headroom from Power HAL", e);
+ return null;
}
}
@Override
- public float getGpuHeadroom(@Nullable GpuHeadroomParamsInternal params) {
+ public GpuHeadroomResult getGpuHeadroom(@NonNull GpuHeadroomParamsInternal params) {
if (mGpuHeadroomIntervalMillis <= 0) {
throw new UnsupportedOperationException();
}
- GpuHeadroomParams halParams = new GpuHeadroomParams();
- if (params != null) {
- halParams.calculationType = params.calculationType;
- }
- synchronized (mGpuHeadroomLock) {
- while (!mGpuHeadroomCache.isEmpty()) {
- if (mGpuHeadroomCache.getFirst().isExpired()) {
- mGpuHeadroomCache.removeFirst();
- } else {
- break;
- }
- }
- for (int i = 0; i < mGpuHeadroomCache.size(); ++i) {
- final GpuHeadroomCacheItem item = mGpuHeadroomCache.get(i);
- if (item.match(params)) {
- item.mExpiredTimeMillis =
- System.currentTimeMillis() + mGpuHeadroomIntervalMillis;
- mGpuHeadroomCache.remove(i);
- mGpuHeadroomCache.add(item);
- return item.mHeadroom;
- }
+ final GpuHeadroomParams halParams = new GpuHeadroomParams();
+ halParams.calculationType = params.calculationType;
+ halParams.calculationWindowMillis = params.calculationWindowMillis;
+ if (halParams.calculationWindowMillis
+ == mDefaultGpuHeadroomCalculationWindowMillis) {
+ synchronized (mGpuHeadroomLock) {
+ final GpuHeadroomResult res = mGpuHeadroomCache.get(halParams);
+ if (res != null) return res;
}
}
// return from HAL directly
try {
- float headroom = mPowerHal.getGpuHeadroom(halParams);
- if (Float.isNaN(headroom)) {
- Slog.wtf(TAG,
- "GPU headroom from Power HAL is NaN");
- return Float.NaN;
- }
- synchronized (mGpuHeadroomLock) {
- mGpuHeadroomCache.add(new GpuHeadroomCacheItem(
- System.currentTimeMillis() + mGpuHeadroomIntervalMillis,
- params, headroom
- ));
+ final GpuHeadroomResult headroom = mPowerHal.getGpuHeadroom(halParams);
+ if (headroom == null) {
+ Slog.wtf(TAG, "GPU headroom from Power HAL is invalid");
+ return null;
+ }
+ if (halParams.calculationWindowMillis
+ == mDefaultGpuHeadroomCalculationWindowMillis) {
+ synchronized (mGpuHeadroomLock) {
+ mGpuHeadroomCache.add(halParams, headroom);
+ }
}
return headroom;
} catch (RemoteException e) {
- return Float.NaN;
+ Slog.e(TAG, "Failed to get GPU headroom from Power HAL", e);
+ return null;
}
}
@Override
- public long getCpuHeadroomMinIntervalMillis() throws RemoteException {
+ public long getCpuHeadroomMinIntervalMillis() {
if (mCpuHeadroomIntervalMillis <= 0) {
throw new UnsupportedOperationException();
}
@@ -1555,7 +1558,7 @@ public final class HintManagerService extends SystemService {
}
@Override
- public long getGpuHeadroomMinIntervalMillis() throws RemoteException {
+ public long getGpuHeadroomMinIntervalMillis() {
if (mGpuHeadroomIntervalMillis <= 0) {
throw new UnsupportedOperationException();
}
@@ -1591,17 +1594,23 @@ public final class HintManagerService extends SystemService {
CpuHeadroomParamsInternal params = new CpuHeadroomParamsInternal();
params.selectionType = CpuHeadroomParams.SelectionType.ALL;
params.usesDeviceHeadroom = true;
- pw.println("CPU headroom: " + Arrays.toString(getCpuHeadroom(params)));
+ CpuHeadroomResult ret = getCpuHeadroom(params);
+ pw.println("CPU headroom: " + (ret == null ? "N/A" : ret.getGlobalHeadroom()));
params = new CpuHeadroomParamsInternal();
params.selectionType = CpuHeadroomParams.SelectionType.PER_CORE;
params.usesDeviceHeadroom = true;
- pw.println("CPU headroom per core: " + Arrays.toString(getCpuHeadroom(params)));
+ ret = getCpuHeadroom(params);
+ pw.println("CPU headroom per core: " + (ret == null ? "N/A"
+ : Arrays.toString(ret.getPerCoreHeadroom())));
} catch (Exception e) {
+ Slog.d(TAG, "Failed to dump CPU headroom", e);
pw.println("CPU headroom: N/A");
}
try {
- pw.println("GPU headroom: " + getGpuHeadroom(null));
+ GpuHeadroomResult ret = getGpuHeadroom(new GpuHeadroomParamsInternal());
+ pw.println("GPU headroom: " + (ret == null ? "N/A" : ret.getGlobalHeadroom()));
} catch (Exception e) {
+ Slog.d(TAG, "Failed to dump GPU headroom", e);
pw.println("GPU headroom: N/A");
}
}
diff --git a/services/core/java/com/android/server/power/hint/flags.aconfig b/services/core/java/com/android/server/power/hint/flags.aconfig
index e56b68c93480..c231f5a6caec 100644
--- a/services/core/java/com/android/server/power/hint/flags.aconfig
+++ b/services/core/java/com/android/server/power/hint/flags.aconfig
@@ -21,3 +21,10 @@ flag {
description: "Set reset_on_fork flag."
bug: "370988407"
}
+
+flag {
+ name: "cpu_headroom_affinity_check"
+ namespace: "game"
+ description: "Check affinity on CPU headroom."
+ bug: "346604998"
+}
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 606bd1dd0f3f..63e8d9973237 100644
--- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
+++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
@@ -26,6 +26,7 @@ import android.os.BatteryUsageStatsQuery;
import android.os.Handler;
import android.os.Process;
import android.util.Log;
+import android.util.LogWriter;
import android.util.Slog;
import android.util.SparseArray;
@@ -34,6 +35,7 @@ import com.android.internal.os.CpuScalingPolicies;
import com.android.internal.os.MonotonicClock;
import com.android.internal.os.PowerProfile;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -44,6 +46,8 @@ import java.util.List;
*/
public class BatteryUsageStatsProvider {
private static final String TAG = "BatteryUsageStatsProv";
+ private static final boolean DEBUG = false;
+
private final Context mContext;
private final PowerAttributor mPowerAttributor;
private final PowerStatsStore mPowerStatsStore;
@@ -262,17 +266,25 @@ public class BatteryUsageStatsProvider {
private BatteryUsageStats getBatteryUsageStats(BatteryStatsImpl stats,
BatteryUsageStatsQuery query, long currentTimeMs) {
+ BatteryUsageStats batteryUsageStats;
if ((query.getFlags()
& BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_ACCUMULATED) != 0) {
- return getAccumulatedBatteryUsageStats(stats, query, currentTimeMs);
+ batteryUsageStats = getAccumulatedBatteryUsageStats(stats, query, currentTimeMs);
} else if (query.getAggregatedToTimestamp() == 0) {
BatteryUsageStats.Builder builder = computeBatteryUsageStats(stats, query,
query.getMonotonicStartTime(),
query.getMonotonicEndTime(), currentTimeMs);
- return builder.build();
+ batteryUsageStats = builder.build();
} else {
- return getAggregatedBatteryUsageStats(stats, query);
+ batteryUsageStats = getAggregatedBatteryUsageStats(stats, query);
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "query = " + query);
+ PrintWriter pw = new PrintWriter(new LogWriter(Log.DEBUG, TAG));
+ batteryUsageStats.dump(pw, "");
+ pw.flush();
}
+ return batteryUsageStats;
}
private BatteryUsageStats getAccumulatedBatteryUsageStats(BatteryStatsImpl stats,
@@ -319,7 +331,7 @@ public class BatteryUsageStatsProvider {
if (accumulatedStats.builder == null) {
accumulatedStats.builder = new BatteryUsageStats.Builder(
- stats.getCustomEnergyConsumerNames(), false, true, true, true, 0);
+ stats.getCustomEnergyConsumerNames(), true, true, true, 0);
accumulatedStats.startWallClockTime = stats.getStartClockTime();
accumulatedStats.builder.setStatsStartTimestamp(accumulatedStats.startWallClockTime);
}
@@ -338,8 +350,6 @@ public class BatteryUsageStatsProvider {
private BatteryUsageStats.Builder computeBatteryUsageStats(BatteryStatsImpl stats,
BatteryUsageStatsQuery query, long monotonicStartTime, long monotonicEndTime,
long currentTimeMs) {
- final boolean includePowerModels = (query.getFlags()
- & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_MODELS) != 0;
final boolean includeProcessStateData = ((query.getFlags()
& BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_PROCESS_STATE_DATA) != 0)
&& stats.isProcessStateDataAvailable();
@@ -353,8 +363,7 @@ public class BatteryUsageStatsProvider {
}
final BatteryUsageStats.Builder batteryUsageStatsBuilder = new BatteryUsageStats.Builder(
- customEnergyConsumerNames, includePowerModels,
- includeProcessStateData, query.isScreenStateDataNeeded(),
+ customEnergyConsumerNames, includeProcessStateData, query.isScreenStateDataNeeded(),
query.isPowerStateDataNeeded(), minConsumedPowerThreshold);
synchronized (stats) {
@@ -444,8 +453,6 @@ public class BatteryUsageStatsProvider {
private BatteryUsageStats getAggregatedBatteryUsageStats(BatteryStatsImpl stats,
BatteryUsageStatsQuery query) {
- final boolean includePowerModels = (query.getFlags()
- & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_MODELS) != 0;
final boolean includeProcessStateData = ((query.getFlags()
& BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_PROCESS_STATE_DATA) != 0)
&& stats.isProcessStateDataAvailable();
@@ -453,7 +460,7 @@ public class BatteryUsageStatsProvider {
final String[] customEnergyConsumerNames = stats.getCustomEnergyConsumerNames();
final BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(
- customEnergyConsumerNames, includePowerModels, includeProcessStateData,
+ customEnergyConsumerNames, includeProcessStateData,
query.isScreenStateDataNeeded(), query.isPowerStateDataNeeded(),
minConsumedPowerThreshold);
if (mPowerStatsStore == null) {
diff --git a/services/core/java/com/android/server/power/stats/processor/PowerStatsExporter.java b/services/core/java/com/android/server/power/stats/processor/PowerStatsExporter.java
index b2442c81b2e5..ef0e63bece90 100644
--- a/services/core/java/com/android/server/power/stats/processor/PowerStatsExporter.java
+++ b/services/core/java/com/android/server/power/stats/processor/PowerStatsExporter.java
@@ -224,13 +224,12 @@ class PowerStatsExporter {
BatteryConsumer.Key key = getKeyForPartialTotal(batteryUsageStatsBuilder, deviceScope,
powerComponentId, screenState, powerState);
if (key != null) {
- deviceScope.addConsumedPower(key, totalPower[0],
- BatteryConsumer.POWER_MODEL_UNDEFINED);
+ deviceScope.addConsumedPower(key, totalPower[0]);
deviceScope.addUsageDurationMillis(key, durationMs[0]);
}
key = deviceScope.getKey(powerComponentId, BatteryConsumer.PROCESS_STATE_UNSPECIFIED);
if (key != null) {
- deviceScope.addConsumedPower(key, totalPower[0], BatteryConsumer.POWER_MODEL_UNDEFINED);
+ deviceScope.addConsumedPower(key, totalPower[0]);
deviceScope.addUsageDurationMillis(key, durationMs[0]);
}
}
@@ -373,7 +372,7 @@ class PowerStatsExporter {
BatteryConsumer.Key key = builder.getKey(powerComponentId, procState,
resultScreenState, resultPowerState);
if (key != null) {
- builder.addConsumedPower(key, power, BatteryConsumer.POWER_MODEL_UNDEFINED);
+ builder.addConsumedPower(key, power);
builder.addUsageDurationMillis(key, duration);
}
}
@@ -384,8 +383,7 @@ class PowerStatsExporter {
BatteryConsumer.PROCESS_STATE_UNSPECIFIED);
if (key != null) {
builder.addConsumedPower(key,
- powerByProcState[BatteryConsumer.PROCESS_STATE_UNSPECIFIED],
- BatteryConsumer.POWER_MODEL_UNDEFINED);
+ powerByProcState[BatteryConsumer.PROCESS_STATE_UNSPECIFIED]);
builder.addUsageDurationMillis(key,
durationByProcState[BatteryConsumer.PROCESS_STATE_UNSPECIFIED]);
}
@@ -399,11 +397,9 @@ class PowerStatsExporter {
BatteryConsumer.Key key = getKeyForPartialTotal(batteryUsageStatsBuilder, allAppsScope,
powerComponentId, screenState, powerState);
if (key != null) {
- allAppsScope.addConsumedPower(key, powerAllApps,
- BatteryConsumer.POWER_MODEL_UNDEFINED);
+ allAppsScope.addConsumedPower(key, powerAllApps);
}
- allAppsScope.addConsumedPower(powerComponentId, powerAllApps,
- BatteryConsumer.POWER_MODEL_UNDEFINED);
+ allAppsScope.addConsumedPower(powerComponentId, powerAllApps);
}
@Nullable
diff --git a/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java b/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java
index e8723b91a541..2cc08c327b71 100644
--- a/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java
+++ b/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java
@@ -46,7 +46,9 @@ import com.android.server.SystemService;
import com.android.server.pm.UserManagerInternal;
import com.android.server.security.advancedprotection.features.AdvancedProtectionHook;
import com.android.server.security.advancedprotection.features.AdvancedProtectionProvider;
+import com.android.server.security.advancedprotection.features.DisallowCellular2GAdvancedProtectionHook;
import com.android.server.security.advancedprotection.features.DisallowInstallUnknownSourcesAdvancedProtectionHook;
+import com.android.server.security.advancedprotection.features.MemoryTaggingExtensionHook;
import java.io.FileDescriptor;
import java.util.ArrayList;
@@ -80,6 +82,12 @@ public class AdvancedProtectionService extends IAdvancedProtectionService.Stub
if (android.security.Flags.aapmFeatureDisableInstallUnknownSources()) {
mHooks.add(new DisallowInstallUnknownSourcesAdvancedProtectionHook(mContext, enabled));
}
+ if (android.security.Flags.aapmFeatureMemoryTaggingExtension()) {
+ mHooks.add(new MemoryTaggingExtensionHook(mContext, enabled));
+ }
+ if (android.security.Flags.aapmFeatureDisableCellular2g()) {
+ mHooks.add(new DisallowCellular2GAdvancedProtectionHook(mContext, enabled));
+ }
}
// Only for tests
diff --git a/services/core/java/com/android/server/security/advancedprotection/features/DisallowCellular2GAdvancedProtectionHook.java b/services/core/java/com/android/server/security/advancedprotection/features/DisallowCellular2GAdvancedProtectionHook.java
new file mode 100644
index 000000000000..b9c8d3dc5319
--- /dev/null
+++ b/services/core/java/com/android/server/security/advancedprotection/features/DisallowCellular2GAdvancedProtectionHook.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.security.advancedprotection.features;
+
+import static android.security.advancedprotection.AdvancedProtectionManager.ADVANCED_PROTECTION_SYSTEM_ENTITY;
+import static android.security.advancedprotection.AdvancedProtectionManager.FEATURE_ID_DISALLOW_CELLULAR_2G;
+
+import android.annotation.NonNull;
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.os.UserManager;
+import android.security.advancedprotection.AdvancedProtectionFeature;
+import android.telephony.TelephonyManager;
+import android.util.Slog;
+
+/** @hide */
+public final class DisallowCellular2GAdvancedProtectionHook extends AdvancedProtectionHook {
+ private static final String TAG = "AdvancedProtectionDisallowCellular2G";
+
+ private final AdvancedProtectionFeature mFeature =
+ new AdvancedProtectionFeature(FEATURE_ID_DISALLOW_CELLULAR_2G);
+ private final DevicePolicyManager mDevicePolicyManager;
+ private final TelephonyManager mTelephonyManager;
+
+ public DisallowCellular2GAdvancedProtectionHook(@NonNull Context context, boolean enabled) {
+ super(context, enabled);
+ mDevicePolicyManager = context.getSystemService(DevicePolicyManager.class);
+ mTelephonyManager = context.getSystemService(TelephonyManager.class);
+
+ setPolicy(enabled);
+ }
+
+ @NonNull
+ @Override
+ public AdvancedProtectionFeature getFeature() {
+ return mFeature;
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return mTelephonyManager.isDataCapable();
+ }
+
+ private void setPolicy(boolean enabled) {
+ Slog.i(TAG, "setPolicy called with " + enabled);
+
+ if (enabled) {
+ Slog.d(TAG, "Setting DISALLOW_CELLULAR_2G_GLOBALLY restriction");
+ mDevicePolicyManager.addUserRestrictionGlobally(
+ ADVANCED_PROTECTION_SYSTEM_ENTITY, UserManager.DISALLOW_CELLULAR_2G);
+ } else {
+ Slog.d(TAG, "Clearing DISALLOW_CELLULAR_2G_GLOBALLY restriction");
+ mDevicePolicyManager.clearUserRestrictionGlobally(
+ ADVANCED_PROTECTION_SYSTEM_ENTITY, UserManager.DISALLOW_CELLULAR_2G);
+ }
+ }
+
+ @Override
+ public void onAdvancedProtectionChanged(boolean enabled) {
+ setPolicy(enabled);
+
+ // Leave 2G disabled even if APM is disabled.
+ if (!enabled) {
+ long oldAllowedTypes =
+ mTelephonyManager.getAllowedNetworkTypesForReason(
+ TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G);
+ long newAllowedTypes = oldAllowedTypes & ~TelephonyManager.NETWORK_CLASS_BITMASK_2G;
+ mTelephonyManager.setAllowedNetworkTypesForReason(
+ TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G, newAllowedTypes);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/security/advancedprotection/features/MemoryTaggingExtensionHook.java b/services/core/java/com/android/server/security/advancedprotection/features/MemoryTaggingExtensionHook.java
new file mode 100644
index 000000000000..d3d39371e883
--- /dev/null
+++ b/services/core/java/com/android/server/security/advancedprotection/features/MemoryTaggingExtensionHook.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.security.advancedprotection.features;
+
+import static android.security.advancedprotection.AdvancedProtectionManager.ADVANCED_PROTECTION_SYSTEM_ENTITY;
+import static android.security.advancedprotection.AdvancedProtectionManager.FEATURE_ID_ENABLE_MTE;
+
+import android.annotation.NonNull;
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.os.SystemProperties;
+import android.security.advancedprotection.AdvancedProtectionFeature;
+import android.util.Slog;
+
+/** @hide */
+public final class MemoryTaggingExtensionHook
+ extends AdvancedProtectionHook {
+ private static final String TAG = "AdvancedProtectionMTE";
+ private static final String MTE_DPM_SYSTEM_PROPERTY =
+ "ro.arm64.memtag.bootctl_device_policy_manager";
+ private static final String MTE_SETTINGS_SYSTEM_PROPERTY =
+ "ro.arm64.memtag.bootctl_settings_toggle";
+
+ private final AdvancedProtectionFeature mFeature = new AdvancedProtectionFeature(
+ FEATURE_ID_ENABLE_MTE);
+ private final DevicePolicyManager mDevicePolicyManager;
+
+ public MemoryTaggingExtensionHook(@NonNull Context context,
+ boolean enabled) {
+ super(context, enabled);
+ mDevicePolicyManager = context.getSystemService(DevicePolicyManager.class);
+ onAdvancedProtectionChanged(enabled);
+ }
+
+ @NonNull
+ @Override
+ public AdvancedProtectionFeature getFeature() {
+ return mFeature;
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return SystemProperties.getBoolean(MTE_DPM_SYSTEM_PROPERTY,
+ SystemProperties.getBoolean(MTE_SETTINGS_SYSTEM_PROPERTY, false));
+ }
+
+ @Override
+ public void onAdvancedProtectionChanged(boolean enabled) {
+ if (!isAvailable()) {
+ Slog.i(TAG, "MTE unavailable on device, skipping.");
+ return;
+ }
+ final int mtePolicy;
+ if (enabled) {
+ mtePolicy = DevicePolicyManager.MTE_ENABLED;
+ } else {
+ mtePolicy = DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY;
+ }
+
+ Slog.d(TAG, "Setting MTE state to " + mtePolicy);
+ try {
+ mDevicePolicyManager.setMtePolicy(ADVANCED_PROTECTION_SYSTEM_ENTITY, mtePolicy);
+ } catch (UnsupportedOperationException e) {
+ Slog.i(TAG, "Setting MTE policy unsupported", e);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/security/intrusiondetection/DataAggregator.java b/services/core/java/com/android/server/security/intrusiondetection/DataAggregator.java
index 06e9dcdcbedd..0ea88e8523f0 100644
--- a/services/core/java/com/android/server/security/intrusiondetection/DataAggregator.java
+++ b/services/core/java/com/android/server/security/intrusiondetection/DataAggregator.java
@@ -36,6 +36,9 @@ public class DataAggregator {
private static final int MSG_DISABLE = 2;
private static final int STORED_EVENTS_SIZE_LIMIT = 1024;
+ private static final IntrusionDetectionAdminReceiver ADMIN_RECEIVER =
+ new IntrusionDetectionAdminReceiver();
+
private final IntrusionDetectionService mIntrusionDetectionService;
private final ArrayList<DataSource> mDataSources;
@@ -60,10 +63,19 @@ public class DataAggregator {
* Initialize DataSources
* @return Whether the initialization succeeds.
*/
- // TODO: Add the corresponding data sources
public boolean initialize() {
SecurityLogSource securityLogSource = new SecurityLogSource(mContext, this);
mDataSources.add(securityLogSource);
+
+ NetworkLogSource networkLogSource = new NetworkLogSource(mContext, this);
+ ADMIN_RECEIVER.setNetworkLogEventCallback(networkLogSource);
+ mDataSources.add(networkLogSource);
+
+ for (DataSource ds : mDataSources) {
+ if (!ds.initialize()) {
+ return false;
+ }
+ }
return true;
}
diff --git a/services/core/java/com/android/server/security/intrusiondetection/DataSource.java b/services/core/java/com/android/server/security/intrusiondetection/DataSource.java
index 0bc448245b76..61fac46be82d 100644
--- a/services/core/java/com/android/server/security/intrusiondetection/DataSource.java
+++ b/services/core/java/com/android/server/security/intrusiondetection/DataSource.java
@@ -18,6 +18,11 @@ package com.android.server.security.intrusiondetection;
public interface DataSource {
/**
+ * Initialize the data source.
+ */
+ boolean initialize();
+
+ /**
* Enable the data collection.
*/
void enable();
diff --git a/services/core/java/com/android/server/security/intrusiondetection/IntrusionDetectionAdminReceiver.java b/services/core/java/com/android/server/security/intrusiondetection/IntrusionDetectionAdminReceiver.java
new file mode 100644
index 000000000000..dba7374fe02a
--- /dev/null
+++ b/services/core/java/com/android/server/security/intrusiondetection/IntrusionDetectionAdminReceiver.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.security.intrusiondetection;
+
+import android.app.admin.DeviceAdminReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Slog;
+
+public class IntrusionDetectionAdminReceiver extends DeviceAdminReceiver {
+ private static final String TAG = "IntrusionDetectionAdminReceiver";
+
+ private static NetworkLogSource sNetworkLogSource;
+
+ @Override
+ public void onNetworkLogsAvailable(
+ Context context, Intent intent, long batchToken, int networkLogsCount) {
+ if (sNetworkLogSource != null) {
+ sNetworkLogSource.onNetworkLogsAvailable(batchToken);
+ } else {
+ Slog.w(TAG, "Network log receiver is not initialized");
+ }
+ }
+
+ public void setNetworkLogEventCallback(NetworkLogSource networkLogSource) {
+ sNetworkLogSource = networkLogSource;
+ }
+}
diff --git a/services/core/java/com/android/server/security/intrusiondetection/NetworkLogSource.java b/services/core/java/com/android/server/security/intrusiondetection/NetworkLogSource.java
new file mode 100644
index 000000000000..1c93d3f9c6a1
--- /dev/null
+++ b/services/core/java/com/android/server/security/intrusiondetection/NetworkLogSource.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.security.intrusiondetection;
+
+import android.app.admin.ConnectEvent;
+import android.app.admin.DevicePolicyManager;
+import android.app.admin.DnsEvent;
+import android.app.admin.NetworkEvent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.security.intrusiondetection.IntrusionDetectionEvent;
+import android.util.Slog;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class NetworkLogSource implements DataSource {
+
+ private static final String TAG = "IntrusionDetectionEvent NetworkLogSource";
+
+ private DevicePolicyManager mDpm;
+ private ComponentName mAdmin;
+ private DataAggregator mDataAggregator;
+
+ public NetworkLogSource(Context context, DataAggregator dataAggregator) {
+ mDataAggregator = dataAggregator;
+ mDpm = context.getSystemService(DevicePolicyManager.class);
+ mAdmin = new ComponentName(context, IntrusionDetectionAdminReceiver.class);
+ }
+
+ @Override
+ public boolean initialize() {
+ try {
+ if (!mDpm.isAdminActive(mAdmin)) {
+ Slog.e(TAG, "Admin " + mAdmin.flattenToString() + "is not active admin");
+ return false;
+ }
+ } catch (SecurityException e) {
+ Slog.e(TAG, "Security exception in initialize: ", e);
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public void enable() {
+ enableNetworkLog();
+ }
+
+ @Override
+ public void disable() {
+ disableNetworkLog();
+ }
+
+ private void enableNetworkLog() {
+ if (!isNetworkLogEnabled()) {
+ mDpm.setNetworkLoggingEnabled(mAdmin, true);
+ }
+ }
+
+ private void disableNetworkLog() {
+ if (isNetworkLogEnabled()) {
+ mDpm.setNetworkLoggingEnabled(mAdmin, false);
+ }
+ }
+
+ private boolean isNetworkLogEnabled() {
+ return mDpm.isNetworkLoggingEnabled(mAdmin);
+ }
+
+ /**
+ * Retrieve network logs when onNetworkLogsAvailable callback is received.
+ *
+ * @param batchToken The token representing the current batch of network logs.
+ */
+ public void onNetworkLogsAvailable(long batchToken) {
+ List<NetworkEvent> events;
+ try {
+ events = mDpm.retrieveNetworkLogs(mAdmin, batchToken);
+ } catch (SecurityException e) {
+ Slog.e(
+ TAG,
+ "Admin "
+ + mAdmin.flattenToString()
+ + "does not have permission to retrieve network logs",
+ e);
+ return;
+ }
+ if (events == null) {
+ if (!isNetworkLogEnabled()) {
+ Slog.w(TAG, "Network logging is disabled");
+ } else {
+ Slog.e(TAG, "Invalid batch token: " + batchToken);
+ }
+ return;
+ }
+
+ List<IntrusionDetectionEvent> intrusionDetectionEvents =
+ events.stream()
+ .filter(event -> event != null)
+ .map(event -> toIntrusionDetectionEvent(event))
+ .collect(Collectors.toList());
+ mDataAggregator.addBatchData(intrusionDetectionEvents);
+ }
+
+ private IntrusionDetectionEvent toIntrusionDetectionEvent(NetworkEvent event) {
+ if (event instanceof DnsEvent) {
+ DnsEvent dnsEvent = (DnsEvent) event;
+ return new IntrusionDetectionEvent(dnsEvent);
+ } else if (event instanceof ConnectEvent) {
+ ConnectEvent connectEvent = (ConnectEvent) event;
+ return new IntrusionDetectionEvent(connectEvent);
+ }
+ throw new IllegalArgumentException(
+ "Invalid event type with ID: "
+ + event.getId()
+ + "from package: "
+ + event.getPackageName());
+ }
+}
diff --git a/services/core/java/com/android/server/security/intrusiondetection/SecurityLogSource.java b/services/core/java/com/android/server/security/intrusiondetection/SecurityLogSource.java
index 226f9d879cab..c5f736e383b2 100644
--- a/services/core/java/com/android/server/security/intrusiondetection/SecurityLogSource.java
+++ b/services/core/java/com/android/server/security/intrusiondetection/SecurityLogSource.java
@@ -22,6 +22,7 @@ import android.app.admin.DevicePolicyManager;
import android.app.admin.SecurityLog.SecurityEvent;
import android.content.Context;
import android.security.intrusiondetection.IntrusionDetectionEvent;
+import android.util.Slog;
import java.util.List;
import java.util.concurrent.Executor;
@@ -33,7 +34,7 @@ public class SecurityLogSource implements DataSource {
private static final String TAG = "IntrusionDetection SecurityLogSource";
- private SecurityEventCallback mEventCallback = new SecurityEventCallback();
+ private SecurityEventCallback mEventCallback;
private DevicePolicyManager mDpm;
private Executor mExecutor;
private DataAggregator mDataAggregator;
@@ -42,9 +43,26 @@ public class SecurityLogSource implements DataSource {
mDataAggregator = dataAggregator;
mDpm = context.getSystemService(DevicePolicyManager.class);
mExecutor = Executors.newSingleThreadExecutor();
+ }
+
+ @Override
+ public boolean initialize() {
+ // Confirm caller is system and the device is managed. Otherwise logs will
+ // be redacted.
+ try {
+ if (!mDpm.isDeviceManaged()) {
+ Slog.e(TAG, "Caller does not have device owner permissions");
+ return false;
+ }
+ } catch (SecurityException e) {
+ Slog.e(TAG, "Security exception in initialize: ", e);
+ return false;
+ }
mEventCallback = new SecurityEventCallback();
+ return true;
}
+
@Override
@RequiresPermission(permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
public void enable() {
@@ -72,12 +90,8 @@ public class SecurityLogSource implements DataSource {
}
}
- /**
- * Check if security audit logging is enabled for the caller.
- *
- * @return Whether security audit logging is enabled.
- */
- public boolean isAuditLogEnabled() {
+ @RequiresPermission(permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
+ private boolean isAuditLogEnabled() {
return mDpm.isAuditLogEnabled();
}
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index 887e1861f789..708bca71eced 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -2528,22 +2528,24 @@ public class TrustManagerService extends SystemService {
}
private void notifyDeviceLockedListenersForUser(int userId, boolean locked) {
- int numListeners = mDeviceLockedStateListeners.beginBroadcast();
- try {
- IntStream.range(0, numListeners).forEach(i -> {
- try {
- Integer uid = (Integer) mDeviceLockedStateListeners.getBroadcastCookie(i);
- if (userId == uid.intValue()) {
- mDeviceLockedStateListeners.getBroadcastItem(i)
- .onDeviceLockedStateChanged(locked);
+ synchronized (mDeviceLockedStateListeners) {
+ int numListeners = mDeviceLockedStateListeners.beginBroadcast();
+ try {
+ IntStream.range(0, numListeners).forEach(i -> {
+ try {
+ Integer uid = (Integer) mDeviceLockedStateListeners.getBroadcastCookie(i);
+ if (userId == uid.intValue()) {
+ mDeviceLockedStateListeners.getBroadcastItem(i)
+ .onDeviceLockedStateChanged(locked);
+ }
+ } catch (RemoteException re) {
+ Log.i(TAG, "Service died", re);
}
- } catch (RemoteException re) {
- Log.i(TAG, "Service died", re);
- }
- });
+ });
- } finally {
- mDeviceLockedStateListeners.finishBroadcast();
+ } finally {
+ mDeviceLockedStateListeners.finishBroadcast();
+ }
}
}
}
diff --git a/services/core/java/com/android/server/utils/WatchableImpl.java b/services/core/java/com/android/server/utils/WatchableImpl.java
index 8a04ccf2dbc6..fec435154c83 100644
--- a/services/core/java/com/android/server/utils/WatchableImpl.java
+++ b/services/core/java/com/android/server/utils/WatchableImpl.java
@@ -33,6 +33,7 @@ public class WatchableImpl implements Watchable {
/**
* The list of observers.
*/
+ @GuardedBy("mObservers")
protected final ArrayList<Watcher> mObservers = new ArrayList<>();
/**
@@ -83,7 +84,9 @@ public class WatchableImpl implements Watchable {
* @return The number of registered observers.
*/
public int registeredObserverCount() {
- return mObservers.size();
+ synchronized (mObservers) {
+ return mObservers.size();
+ }
}
/**
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 0337f5f4f9c8..bbef5785dfcb 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -3181,7 +3181,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
throw new IllegalArgumentException("Invalid crop rect supplied: " + crop);
}
int orientation = screenOrientations[i];
- if (orientation == ORIENTATION_UNKNOWN && cropMap.size() > 1) {
+ if (orientation == ORIENTATION_UNKNOWN && crops.size() > 1) {
throw new IllegalArgumentException("Invalid crops supplied: the UNKNOWN"
+ "screen orientation should only be used in a singleton map");
}
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 4b7e74af474c..dd769173fb34 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -25,7 +25,6 @@ import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFI
import static android.view.WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY;
import static android.view.WindowManager.TRANSIT_FLAG_IS_RECENTS;
-import static com.android.internal.util.DumpUtils.dumpSparseArray;
import static com.android.internal.util.DumpUtils.dumpSparseArrayValues;
import static com.android.server.accessibility.AccessibilityTraceFileProto.ENTRY;
import static com.android.server.accessibility.AccessibilityTraceFileProto.MAGIC_NUMBER;
@@ -49,23 +48,15 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowTracingLegacy.WINSCOPE_EXT;
import android.accessibilityservice.AccessibilityTrace;
-import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Application;
import android.content.Context;
import android.content.pm.PackageManagerInternal;
-import android.graphics.BLASTBufferQueue;
-import android.graphics.Canvas;
-import android.graphics.Color;
import android.graphics.Insets;
import android.graphics.Matrix;
-import android.graphics.Paint;
import android.graphics.Path;
-import android.graphics.PixelFormat;
import android.graphics.Point;
-import android.graphics.PorterDuff.Mode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
@@ -83,25 +74,16 @@ import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
-import android.util.TypedValue;
import android.util.proto.ProtoOutputStream;
import android.view.Display;
import android.view.MagnificationSpec;
import android.view.Surface;
-import android.view.Surface.OutOfResourcesException;
-import android.view.SurfaceControl;
import android.view.ViewConfiguration;
import android.view.WindowInfo;
import android.view.WindowManager;
import android.view.WindowManager.TransitionFlags;
import android.view.WindowManager.TransitionType;
-import android.view.WindowManagerPolicyConstants;
-import android.view.animation.DecelerateInterpolator;
-import android.view.animation.Interpolator;
-import com.android.internal.R;
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.SomeArgs;
import com.android.internal.util.TraceBuffer;
import com.android.internal.util.function.pooled.PooledLambda;
@@ -111,7 +93,6 @@ import com.android.server.wm.AccessibilityWindowsPopulator.AccessibilityWindow;
import com.android.server.wm.WindowManagerInternal.AccessibilityControllerInternal;
import com.android.server.wm.WindowManagerInternal.MagnificationCallbacks;
import com.android.server.wm.WindowManagerInternal.WindowsForAccessibilityCallback;
-import com.android.window.flags.Flags;
import java.io.File;
import java.io.IOException;
@@ -301,36 +282,6 @@ final class AccessibilityController {
}
}
- /** It is only used by unit test. */
- @VisibleForTesting
- Surface forceShowMagnifierSurface(int displayId) {
- final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
- if (displayMagnifier != null) {
- displayMagnifier.mMagnifiedViewport.mWindow.setAlpha(DisplayMagnifier.MagnifiedViewport
- .ViewportWindow.AnimationController.MAX_ALPHA);
- return displayMagnifier.mMagnifiedViewport.mWindow.mSurface;
- }
- return null;
- }
-
- void onWindowLayersChanged(int displayId) {
- if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK
- | FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK)) {
- mAccessibilityTracing.logTrace(TAG + ".onWindowLayersChanged",
- FLAGS_MAGNIFICATION_CALLBACK | FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK,
- "displayId=" + displayId);
- }
- final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
- if (displayMagnifier != null) {
- displayMagnifier.onWindowLayersChanged();
- }
- final WindowsForAccessibilityObserver windowsForA11yObserver =
- mWindowsForAccessibilityObserver.get(displayId);
- if (windowsForA11yObserver != null) {
- windowsForA11yObserver.scheduleComputeChangedWindows();
- }
- }
-
void onDisplaySizeChanged(DisplayContent displayContent) {
if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK
@@ -563,9 +514,6 @@ final class AccessibilityController {
}
void dump(PrintWriter pw, String prefix) {
- dumpSparseArray(pw, prefix, mDisplayMagnifiers, "magnification display",
- (index, key) -> pw.printf("%sDisplay #%d:", prefix + " ", key),
- dm -> dm.dump(pw, ""));
dumpSparseArrayValues(pw, prefix, mWindowsForAccessibilityObserver,
"windows for accessibility observer");
mAccessibilityWindowsPopulator.dump(pw, prefix);
@@ -623,7 +571,6 @@ final class AccessibilityController {
private final Context mDisplayContext;
private final WindowManagerService mService;
- private final MagnifiedViewport mMagnifiedViewport;
private final Handler mHandler;
private final DisplayContent mDisplayContent;
private final Display mDisplay;
@@ -660,8 +607,6 @@ final class AccessibilityController {
mDisplay = display;
mHandler = new MyHandler(mService.mH.getLooper());
mUserContextChangedNotifier = new UserContextChangedNotifier(mHandler);
- mMagnifiedViewport = Flags.alwaysDrawMagnificationFullscreenBorder()
- ? null : new MagnifiedViewport();
mAccessibilityTracing =
AccessibilityController.getAccessibilityControllerInternal(mService);
mLongAnimationDuration = mDisplayContext.getResources().getInteger(
@@ -703,10 +648,6 @@ final class AccessibilityController {
} else {
mMagnificationSpec.clear();
}
-
- if (!Flags.alwaysDrawMagnificationFullscreenBorder()) {
- mMagnifiedViewport.setShowMagnifiedBorderIfNeeded();
- }
}
void setFullscreenMagnificationActivated(boolean activated) {
@@ -715,10 +656,6 @@ final class AccessibilityController {
FLAGS_MAGNIFICATION_CALLBACK, "activated=" + activated);
}
mIsFullscreenMagnificationActivated = activated;
- if (!Flags.alwaysDrawMagnificationFullscreenBorder()) {
- mMagnifiedViewport.setMagnifiedRegionBorderShown(activated, true);
- mMagnifiedViewport.showMagnificationBoundsIfNeeded();
- }
}
boolean isFullscreenMagnificationActivated() {
@@ -729,18 +666,6 @@ final class AccessibilityController {
return mIsFullscreenMagnificationActivated;
}
- void onWindowLayersChanged() {
- if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
- mAccessibilityTracing.logTrace(
- LOG_TAG + ".onWindowLayersChanged", FLAGS_MAGNIFICATION_CALLBACK);
- }
- if (DEBUG_LAYERS) {
- Slog.i(LOG_TAG, "Layers changed.");
- }
- recomputeBounds();
- mService.scheduleAnimationLocked();
- }
-
void onDisplaySizeChanged(DisplayContent displayContent) {
if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
mAccessibilityTracing.logTrace(LOG_TAG + ".onDisplaySizeChanged",
@@ -753,9 +678,6 @@ final class AccessibilityController {
}
recomputeBounds();
- if (!Flags.alwaysDrawMagnificationFullscreenBorder()) {
- mMagnifiedViewport.onDisplaySizeChanged();
- }
mHandler.sendEmptyMessage(MyHandler.MESSAGE_NOTIFY_DISPLAY_SIZE_CHANGED);
}
@@ -926,10 +848,6 @@ final class AccessibilityController {
if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
mAccessibilityTracing.logTrace(LOG_TAG + ".destroy", FLAGS_MAGNIFICATION_CALLBACK);
}
-
- if (!Flags.alwaysDrawMagnificationFullscreenBorder()) {
- mMagnifiedViewport.destroyWindow();
- }
}
void recomputeMagnifiedRegionAndDrawMagnifiedRegionBorderIfNeeded() {
@@ -939,10 +857,6 @@ final class AccessibilityController {
FLAGS_MAGNIFICATION_CALLBACK);
}
recomputeBounds();
-
- if (!Flags.alwaysDrawMagnificationFullscreenBorder()) {
- mMagnifiedViewport.drawWindowIfNeeded();
- }
}
void recomputeBounds() {
@@ -1050,16 +964,9 @@ final class AccessibilityController {
}
visibleWindows.clear();
- if (!Flags.alwaysDrawMagnificationFullscreenBorder()) {
- mMagnifiedViewport.intersectWithDrawBorderInset(screenWidth, screenHeight);
- }
-
final boolean magnifiedChanged =
!mOldMagnificationRegion.equals(mMagnificationRegion);
if (magnifiedChanged) {
- if (!Flags.alwaysDrawMagnificationFullscreenBorder()) {
- mMagnifiedViewport.updateBorderDrawingStatus(screenWidth, screenHeight);
- }
mOldMagnificationRegion.set(mMagnificationRegion);
final SomeArgs args = SomeArgs.obtain();
args.arg1 = Region.obtain(mMagnificationRegion);
@@ -1139,420 +1046,11 @@ final class AccessibilityController {
outSize.set(bounds.width(), bounds.height());
}
- void dump(PrintWriter pw, String prefix) {
- if (!Flags.alwaysDrawMagnificationFullscreenBorder()) {
- mMagnifiedViewport.dump(pw, prefix);
- }
- }
-
- private final class MagnifiedViewport {
-
- private final float mBorderWidth;
- private final int mHalfBorderWidth;
- private final int mDrawBorderInset;
-
- @Nullable private final ViewportWindow mWindow;
-
- private boolean mFullRedrawNeeded;
-
- MagnifiedViewport() {
- mBorderWidth = mDisplayContext.getResources().getDimension(
- com.android.internal.R.dimen.accessibility_magnification_indicator_width);
- mHalfBorderWidth = (int) Math.ceil(mBorderWidth / 2);
- mDrawBorderInset = (int) mBorderWidth / 2;
- mWindow = new ViewportWindow(mDisplayContext);
- }
-
- void updateBorderDrawingStatus(int screenWidth, int screenHeight) {
- mWindow.setBounds(mMagnificationRegion);
- final Rect dirtyRect = mTempRect1;
- if (mFullRedrawNeeded) {
- mFullRedrawNeeded = false;
- dirtyRect.set(mDrawBorderInset, mDrawBorderInset,
- screenWidth - mDrawBorderInset,
- screenHeight - mDrawBorderInset);
- mWindow.invalidate(dirtyRect);
- } else {
- final Region dirtyRegion = mTempRegion3;
- dirtyRegion.set(mMagnificationRegion);
- dirtyRegion.op(mOldMagnificationRegion, Region.Op.XOR);
- dirtyRegion.getBounds(dirtyRect);
- mWindow.invalidate(dirtyRect);
- }
- }
-
- void setShowMagnifiedBorderIfNeeded() {
- // If this message is pending, we are in a rotation animation and do not want
- // to show the border. We will do so when the pending message is handled.
- if (!mHandler.hasMessages(
- MyHandler.MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED)) {
- setMagnifiedRegionBorderShown(
- isFullscreenMagnificationActivated(), true);
- }
- }
-
- // Can be called outside of a surface transaction
- void showMagnificationBoundsIfNeeded() {
- if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
- mAccessibilityTracing.logTrace(LOG_TAG + ".showMagnificationBoundsIfNeeded",
- FLAGS_MAGNIFICATION_CALLBACK);
- }
- mHandler.obtainMessage(MyHandler.MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED)
- .sendToTarget();
- }
-
- void intersectWithDrawBorderInset(int screenWidth, int screenHeight) {
- mMagnificationRegion.op(mDrawBorderInset, mDrawBorderInset,
- screenWidth - mDrawBorderInset, screenHeight - mDrawBorderInset,
- Region.Op.INTERSECT);
- }
-
- void onDisplaySizeChanged() {
- // If fullscreen magnification is activated, hide the border immediately so
- // the user does not see strange artifacts during display size changed caused by
- // rotation or folding/unfolding the device. In the rotation case, the
- // screenshot used for rotation already has the border. After the rotation is
- // completed we will show the border.
- if (isFullscreenMagnificationActivated()) {
- setMagnifiedRegionBorderShown(false, false);
- final long delay = (long) (mLongAnimationDuration
- * mService.getWindowAnimationScaleLocked());
- Message message = mHandler.obtainMessage(
- MyHandler.MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED);
- mHandler.sendMessageDelayed(message, delay);
- }
- mWindow.updateSize();
- }
-
- void setMagnifiedRegionBorderShown(boolean shown, boolean animate) {
- if (mWindow.setShown(shown, animate)) {
- mFullRedrawNeeded = true;
- // Clear the old region, so recomputeBounds will refresh the current region.
- mOldMagnificationRegion.set(0, 0, 0, 0);
- }
- }
-
- void drawWindowIfNeeded() {
- mWindow.postDrawIfNeeded();
- }
-
- void destroyWindow() {
- mWindow.releaseSurface();
- }
-
- void dump(PrintWriter pw, String prefix) {
- mWindow.dump(pw, prefix);
- }
-
- // TODO(291891390): Remove this class when we clean up the flag
- // alwaysDrawMagnificationFullscreenBorder
- private final class ViewportWindow implements Runnable {
- private static final String SURFACE_TITLE = "Magnification Overlay";
-
- private final Region mBounds = new Region();
- private final Rect mDirtyRect = new Rect();
- private final Paint mPaint = new Paint();
-
- private final SurfaceControl mSurfaceControl;
- /** After initialization, it should only be accessed from animation thread. */
- private final SurfaceControl.Transaction mTransaction;
- private final BLASTBufferQueue mBlastBufferQueue;
- private final Surface mSurface;
-
- private final AnimationController mAnimationController;
-
- private boolean mShown;
- private boolean mLastSurfaceShown;
- private int mAlpha;
- private int mPreviousAlpha;
-
- private volatile boolean mInvalidated;
-
- ViewportWindow(Context context) {
- SurfaceControl surfaceControl = null;
- try {
- surfaceControl = mDisplayContent
- .makeOverlay()
- .setName(SURFACE_TITLE)
- .setBLASTLayer()
- .setFormat(PixelFormat.TRANSLUCENT)
- .setCallsite("ViewportWindow")
- .build();
- } catch (OutOfResourcesException oore) {
- /* ignore */
- }
- mSurfaceControl = surfaceControl;
- mDisplay.getRealSize(mScreenSize);
- mBlastBufferQueue = new BLASTBufferQueue(SURFACE_TITLE, mSurfaceControl,
- mScreenSize.x, mScreenSize.y, PixelFormat.RGBA_8888);
-
- final SurfaceControl.Transaction t = mService.mTransactionFactory.get();
- final int layer =
- mService.mPolicy.getWindowLayerFromTypeLw(TYPE_MAGNIFICATION_OVERLAY) *
- WindowManagerPolicyConstants.TYPE_LAYER_MULTIPLIER;
- t.setLayer(mSurfaceControl, layer).setPosition(mSurfaceControl, 0, 0);
- InputMonitor.setTrustedOverlayInputInfo(mSurfaceControl, t,
- mDisplayContent.getDisplayId(), "Magnification Overlay");
- t.apply();
- mTransaction = t;
- mSurface = mBlastBufferQueue.createSurface();
-
- mAnimationController = new AnimationController(context,
- mService.mH.getLooper());
-
- TypedValue typedValue = new TypedValue();
- context.getTheme().resolveAttribute(R.attr.colorActivatedHighlight,
- typedValue, true);
- final int borderColor = context.getColor(typedValue.resourceId);
-
- mPaint.setStyle(Paint.Style.STROKE);
- mPaint.setStrokeWidth(mBorderWidth);
- mPaint.setColor(borderColor);
-
- mInvalidated = true;
- }
-
- /** Returns {@code true} if the state is changed to shown. */
- boolean setShown(boolean shown, boolean animate) {
- synchronized (mService.mGlobalLock) {
- if (mShown == shown) {
- return false;
- }
- mShown = shown;
- mAnimationController.onFrameShownStateChanged(shown, animate);
- if (DEBUG_VIEWPORT_WINDOW) {
- Slog.i(LOG_TAG, "ViewportWindow shown: " + mShown);
- }
- }
- return shown;
- }
-
- @SuppressWarnings("unused")
- // Called reflectively from an animator.
- int getAlpha() {
- synchronized (mService.mGlobalLock) {
- return mAlpha;
- }
- }
-
- void setAlpha(int alpha) {
- synchronized (mService.mGlobalLock) {
- if (mAlpha == alpha) {
- return;
- }
- mAlpha = alpha;
- invalidate(null);
- if (DEBUG_VIEWPORT_WINDOW) {
- Slog.i(LOG_TAG, "ViewportWindow set alpha: " + alpha);
- }
- }
- }
-
- void setBounds(Region bounds) {
- synchronized (mService.mGlobalLock) {
- if (mBounds.equals(bounds)) {
- return;
- }
- mBounds.set(bounds);
- invalidate(mDirtyRect);
- if (DEBUG_VIEWPORT_WINDOW) {
- Slog.i(LOG_TAG, "ViewportWindow set bounds: " + bounds);
- }
- }
- }
-
- void updateSize() {
- synchronized (mService.mGlobalLock) {
- getDisplaySizeLocked(mScreenSize);
- mBlastBufferQueue.update(mSurfaceControl, mScreenSize.x, mScreenSize.y,
- PixelFormat.RGBA_8888);
- invalidate(mDirtyRect);
- }
- }
-
- void invalidate(Rect dirtyRect) {
- if (dirtyRect != null) {
- mDirtyRect.set(dirtyRect);
- } else {
- mDirtyRect.setEmpty();
- }
- mInvalidated = true;
- mService.scheduleAnimationLocked();
- }
-
- void postDrawIfNeeded() {
- if (mInvalidated) {
- mService.mAnimationHandler.post(this);
- }
- }
-
- @Override
- public void run() {
- drawOrRemoveIfNeeded();
- }
-
- /**
- * This method must only be called by animation handler directly to make sure
- * thread safe and there is no lock held outside.
- */
- private void drawOrRemoveIfNeeded() {
- // Drawing variables (alpha, dirty rect, and bounds) access is synchronized
- // using WindowManagerGlobalLock. Grab copies of these values before
- // drawing on the canvas so that drawing can be performed outside of the lock.
- int alpha;
- boolean redrawBounds;
- Rect drawingRect = null;
- Region drawingBounds = null;
- synchronized (mService.mGlobalLock) {
- if (mBlastBufferQueue.mNativeObject == 0) {
- // Complete removal since releaseSurface has been called.
- if (mSurface.isValid()) {
- mTransaction.remove(mSurfaceControl).apply();
- mSurface.release();
- }
- return;
- }
- if (!mInvalidated) {
- return;
- }
- mInvalidated = false;
-
- alpha = mAlpha;
- // For b/325863281, we should ensure the drawn border path is cleared when
- // alpha = 0. Therefore, we cache the last used alpha when drawing as
- // mPreviousAlpha and check it here. If mPreviousAlpha > 0, which means
- // the border is showing now, then we should still redraw the clear path
- // on the canvas so the border is cleared.
- redrawBounds = mAlpha > 0 || mPreviousAlpha > 0;
- if (redrawBounds) {
- drawingBounds = new Region(mBounds);
- // Empty dirty rectangle means unspecified.
- if (mDirtyRect.isEmpty()) {
- mBounds.getBounds(mDirtyRect);
- }
- mDirtyRect.inset(-mHalfBorderWidth, -mHalfBorderWidth);
- drawingRect = new Rect(mDirtyRect);
- if (DEBUG_VIEWPORT_WINDOW) {
- Slog.i(LOG_TAG, "ViewportWindow bounds: " + mBounds);
- Slog.i(LOG_TAG, "ViewportWindow dirty rect: " + mDirtyRect);
- }
- }
- }
-
- final boolean showSurface;
- // Draw without holding WindowManagerGlobalLock.
- if (redrawBounds) {
- Canvas canvas = null;
- try {
- canvas = mSurface.lockCanvas(drawingRect);
- } catch (IllegalArgumentException | OutOfResourcesException e) {
- /* ignore */
- }
- if (canvas == null) {
- return;
- }
- canvas.drawColor(Color.TRANSPARENT, Mode.CLEAR);
- mPaint.setAlpha(alpha);
- canvas.drawPath(drawingBounds.getBoundaryPath(), mPaint);
- mSurface.unlockCanvasAndPost(canvas);
- mPreviousAlpha = alpha;
- }
-
- showSurface = alpha > 0;
-
- if (showSurface && !mLastSurfaceShown) {
- mTransaction.show(mSurfaceControl).apply();
- mLastSurfaceShown = true;
- } else if (!showSurface && mLastSurfaceShown) {
- mTransaction.hide(mSurfaceControl).apply();
- mLastSurfaceShown = false;
- }
- }
-
- @GuardedBy("mService.mGlobalLock")
- void releaseSurface() {
- mBlastBufferQueue.destroy();
- // Post to perform cleanup on the thread which handles mSurface.
- mService.mAnimationHandler.post(this);
- }
-
- void dump(PrintWriter pw, String prefix) {
- pw.println(prefix
- + " mBounds= " + mBounds
- + " mDirtyRect= " + mDirtyRect
- + " mWidth= " + mScreenSize.x
- + " mHeight= " + mScreenSize.y);
- }
-
- private final class AnimationController extends Handler {
- private static final String PROPERTY_NAME_ALPHA = "alpha";
-
- private static final int MIN_ALPHA = 0;
- private static final int MAX_ALPHA = 255;
-
- private static final int MSG_FRAME_SHOWN_STATE_CHANGED = 1;
-
- private final ValueAnimator mShowHideFrameAnimator;
-
- AnimationController(Context context, Looper looper) {
- super(looper);
- mShowHideFrameAnimator = ObjectAnimator.ofInt(ViewportWindow.this,
- PROPERTY_NAME_ALPHA, MIN_ALPHA, MAX_ALPHA);
-
- Interpolator interpolator = new DecelerateInterpolator(2.5f);
- final long longAnimationDuration = context.getResources().getInteger(
- com.android.internal.R.integer.config_longAnimTime);
-
- mShowHideFrameAnimator.setInterpolator(interpolator);
- mShowHideFrameAnimator.setDuration(longAnimationDuration);
- }
-
- void onFrameShownStateChanged(boolean shown, boolean animate) {
- obtainMessage(MSG_FRAME_SHOWN_STATE_CHANGED,
- shown ? 1 : 0, animate ? 1 : 0).sendToTarget();
- }
-
- @Override
- public void handleMessage(Message message) {
- switch (message.what) {
- case MSG_FRAME_SHOWN_STATE_CHANGED: {
- final boolean shown = message.arg1 == 1;
- final boolean animate = message.arg2 == 1;
-
- if (animate) {
- if (mShowHideFrameAnimator.isRunning()) {
- mShowHideFrameAnimator.reverse();
- } else {
- if (shown) {
- mShowHideFrameAnimator.start();
- } else {
- mShowHideFrameAnimator.reverse();
- }
- }
- } else {
- mShowHideFrameAnimator.cancel();
- if (shown) {
- setAlpha(MAX_ALPHA);
- } else {
- setAlpha(MIN_ALPHA);
- }
- }
- } break;
- }
- }
- }
- }
- }
-
private class MyHandler extends Handler {
public static final int MESSAGE_NOTIFY_MAGNIFICATION_REGION_CHANGED = 1;
public static final int MESSAGE_NOTIFY_USER_CONTEXT_CHANGED = 3;
public static final int MESSAGE_NOTIFY_DISPLAY_SIZE_CHANGED = 4;
-
- // TODO(291891390): Remove this field when we clean up the flag
- // alwaysDrawMagnificationFullscreenBorder
- public static final int MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED = 5;
- public static final int MESSAGE_NOTIFY_IME_WINDOW_VISIBILITY_CHANGED = 6;
+ public static final int MESSAGE_NOTIFY_IME_WINDOW_VISIBILITY_CHANGED = 5;
MyHandler(Looper looper) {
super(looper);
@@ -1576,17 +1074,6 @@ final class AccessibilityController {
mCallbacks.onDisplaySizeChanged();
} break;
- case MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED : {
- synchronized (mService.mGlobalLock) {
- if (isFullscreenMagnificationActivated()) {
- if (!Flags.alwaysDrawMagnificationFullscreenBorder()) {
- mMagnifiedViewport.setMagnifiedRegionBorderShown(true, true);
- }
- mService.scheduleAnimationLocked();
- }
- }
- } break;
-
case MESSAGE_NOTIFY_IME_WINDOW_VISIBILITY_CHANGED: {
final boolean shown = message.arg1 == 1;
mCallbacks.onImeWindowVisibilityChanged(shown);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index ae55a1a77873..d2546e49267a 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -3230,8 +3230,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
return false;
}
// If the user preference respects aspect ratio, then it becomes non-resizable.
- return !mAppCompatController.getAppCompatOverrides().getAppCompatAspectRatioOverrides()
- .shouldApplyUserMinAspectRatioOverride();
+ return mAppCompatController.getAppCompatOverrides().getAppCompatAspectRatioOverrides()
+ .userPreferenceCompatibleWithNonResizability();
}
boolean isResizeable() {
diff --git a/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java b/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
index 90c0866d7dff..086b11c25a8d 100644
--- a/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
@@ -135,6 +135,12 @@ class AppCompatAspectRatioOverrides {
&& aspectRatio != USER_MIN_ASPECT_RATIO_FULLSCREEN;
}
+ boolean userPreferenceCompatibleWithNonResizability() {
+ final int aspectRatio = getUserMinAspectRatioOverrideCode();
+ return aspectRatio == USER_MIN_ASPECT_RATIO_UNSET
+ || aspectRatio == USER_MIN_ASPECT_RATIO_FULLSCREEN;
+ }
+
boolean shouldApplyUserFullscreenOverride() {
if (isUserFullscreenOverrideEnabled()) {
final int aspectRatio = getUserMinAspectRatioOverrideCode();
diff --git a/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java b/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java
index 9754595581a0..47d30c98120c 100644
--- a/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java
@@ -164,12 +164,14 @@ class AppCompatCameraOverrides {
* <p>The treatment is enabled when the following conditions are met:
* <ul>
* <li>Feature flag gating the camera compatibility free-form treatment is enabled.
- * <li>Activity is opted in by the device manufacturer with override.
+ * <li>Activity is opted-in using per-app override, or the treatment is enabled for all apps.
* </ul>
*/
boolean shouldApplyFreeformTreatmentForCameraCompat() {
- return Flags.enableCameraCompatForDesktopWindowing() && isChangeEnabled(mActivityRecord,
- OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT);
+ return Flags.enableCameraCompatForDesktopWindowing() && (isChangeEnabled(mActivityRecord,
+ OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT)
+ || mActivityRecord.mWmService.mAppCompatConfiguration
+ .isCameraCompatFreeformWindowingTreatmentEnabled());
}
boolean isOverrideOrientationOnlyForCameraEnabled() {
diff --git a/services/core/java/com/android/server/wm/AppCompatConfiguration.java b/services/core/java/com/android/server/wm/AppCompatConfiguration.java
index 38c6de146293..9a15c4a8bff2 100644
--- a/services/core/java/com/android/server/wm/AppCompatConfiguration.java
+++ b/services/core/java/com/android/server/wm/AppCompatConfiguration.java
@@ -304,6 +304,11 @@ final class AppCompatConfiguration {
// See RefreshCallbackItem for context.
private boolean mIsCameraCompatRefreshCycleThroughStopEnabled = true;
+ // Whether camera compat freeform treatment should be enabled for all eligible activities.
+ // This has the same effect as enabling the per-app override
+ // ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT for every app.
+ private boolean mIsCameraCompatFreeformWindowingTreatmentEnabled = false;
+
// Whether should ignore app requested orientation in response to an app
// calling Activity#setRequestedOrientation. See
// LetterboxUiController#shouldIgnoreRequestedOrientation for details.
@@ -1351,6 +1356,30 @@ final class AppCompatConfiguration {
}
/**
+ * Sets whether the camera compatibility treatment in freeform windowing mode is enabled for
+ * all fixed-orientation apps when using camera.
+ */
+ void setIsCameraCompatFreeformWindowingTreatmentEnabled(boolean enabled) {
+ mIsCameraCompatFreeformWindowingTreatmentEnabled = enabled;
+ }
+
+ /**
+ * Whether the camera compatibility treatment in freeform windowing mode is enabled for all
+ * fixed-orientation apps when using camera.
+ */
+ boolean isCameraCompatFreeformWindowingTreatmentEnabled() {
+ return mIsCameraCompatFreeformWindowingTreatmentEnabled;
+ }
+
+ /**
+ * Resets whether the camera compatibility treatment in freeform windowing mode is enabled for
+ * all fixed-orientation apps when using camera.
+ */
+ void resetIsCameraCompatFreeformWindowingTreatmentEnabled() {
+ mIsCameraCompatFreeformWindowingTreatmentEnabled = false;
+ }
+
+ /**
* Checks whether rotation compat policy for immersive apps that prevents auto rotation
* into non-optimal screen orientation while in fullscreen is enabled at build time. This is
* used when we need to safely initialize a component before the {@link DeviceConfig} flag
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index 49f717e228d2..48e1c069821c 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -346,12 +346,15 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider {
ImeTracker.forLogging().onProgress(statsToken,
ImeTracker.PHASE_WM_SET_REMOTE_TARGET_IME_VISIBILITY);
controlTarget.setImeInputTargetRequestedVisibility(imeVisible);
+ // not all virtual displays have an ImeInsetsSourceProvider, so it is not
+ // guaranteed that the IME will be started when the control target reports its
+ // requested visibility back. Thus, invoking the listener here.
+ invokeOnImeRequestedChangedListener(imeInsetsTarget, statsToken);
} else {
ImeTracker.forLogging().onFailed(statsToken,
ImeTracker.PHASE_WM_SET_REMOTE_TARGET_IME_VISIBILITY);
}
}
- invokeOnImeRequestedChangedListener(imeInsetsTarget, statsToken);
}
// TODO(b/353463205) check callers to see if we can make statsToken @NonNull
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index fe2bcc7a74f3..44f5f51eb623 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -1166,6 +1166,10 @@ public class WindowManagerShellCommand extends ShellCommand {
case "--cameraCompatAspectRatio":
runSetCameraCompatAspectRatio(pw);
break;
+ case "--isCameraCompatFreeformWindowingTreatmentEnabled":
+ runSetBooleanFlag(pw, mAppCompatConfiguration
+ ::setIsCameraCompatFreeformWindowingTreatmentEnabled);
+ break;
default:
getErrPrintWriter().println(
"Error: Unrecognized letterbox style option: " + arg);
@@ -1260,6 +1264,10 @@ public class WindowManagerShellCommand extends ShellCommand {
case "cameraCompatAspectRatio":
mAppCompatConfiguration.resetCameraCompatAspectRatio();
break;
+ case "isCameraCompatFreeformWindowingTreatmentEnabled":
+ mAppCompatConfiguration
+ .resetIsCameraCompatFreeformWindowingTreatmentEnabled();
+ break;
default:
getErrPrintWriter().println(
"Error: Unrecognized letterbox style option: " + arg);
@@ -1371,6 +1379,7 @@ public class WindowManagerShellCommand extends ShellCommand {
mAppCompatConfiguration.resetCameraCompatRefreshEnabled();
mAppCompatConfiguration.resetCameraCompatRefreshCycleThroughStopEnabled();
mAppCompatConfiguration.resetCameraCompatAspectRatio();
+ mAppCompatConfiguration.resetIsCameraCompatFreeformWindowingTreatmentEnabled();
}
}
@@ -1445,6 +1454,10 @@ public class WindowManagerShellCommand extends ShellCommand {
+ mAppCompatConfiguration.isUserAppAspectRatioSettingsEnabled());
pw.println("Is the fullscreen option in user aspect ratio settings enabled: "
+ mAppCompatConfiguration.isUserAppAspectRatioFullscreenEnabled());
+ pw.println("Default aspect ratio for camera compat freeform: "
+ + mAppCompatConfiguration.getCameraCompatAspectRatio());
+ pw.println("Is camera compatibility freeform treatment enabled for all apps: "
+ + mAppCompatConfiguration.isCameraCompatFreeformWindowingTreatmentEnabled());
}
return 0;
}
@@ -1701,10 +1714,13 @@ public class WindowManagerShellCommand extends ShellCommand {
pw.println(" happen using the \"stopped -> resumed\" cycle rather than");
pw.println(" \"paused -> resumed\" cycle.");
pw.println(" --cameraCompatAspectRatio aspectRatio");
- pw.println(" Aspect ratio of letterbox for fixed-orientation camera apps, during ");
+ pw.println(" Aspect ratio of letterbox for fixed-orientation camera apps, during");
pw.println(" freeform camera compat mode. If aspectRatio <= "
+ AppCompatConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO);
pw.println(" it will be ignored.");
+ pw.println(" --isCameraCompatFreeformWindowingTreatmentEnabled [true|1|false|0]");
+ pw.println(" Whether camera compat treatment is enabled in freeform mode for all");
+ pw.println(" eligible apps.");
pw.println(" reset-letterbox-style [aspectRatio|cornerRadius|backgroundType");
pw.println(" |backgroundColor|wallpaperBlurRadius|wallpaperDarkScrimAlpha");
pw.println(" |horizontalPositionMultiplier|verticalPositionMultiplier");
@@ -1714,7 +1730,8 @@ public class WindowManagerShellCommand extends ShellCommand {
pw.println(" |persistentPositionMultiplierForHorizontalReachability");
pw.println(" |persistentPositionMultiplierForVerticalReachability");
pw.println(" |defaultPositionMultiplierForVerticalReachability");
- pw.println(" |cameraCompatAspectRatio]");
+ pw.println(" |cameraCompatAspectRatio");
+ pw.println(" |isCameraCompatFreeformWindowingTreatmentEnabled]");
pw.println(" Resets overrides to default values for specified properties separated");
pw.println(" by space, e.g. 'reset-letterbox-style aspectRatio cornerRadius'.");
pw.println(" If no arguments provided, all values will be reset.");
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index dde213de1d40..5bd70ef57fac 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -23708,24 +23708,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
public void setMtePolicy(int flags, String callerPackageName) {
- final Set<Integer> allowedModes =
- Set.of(
- DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY,
- DevicePolicyManager.MTE_DISABLED,
- DevicePolicyManager.MTE_ENABLED);
- Preconditions.checkArgument(
- allowedModes.contains(flags), "Provided mode is not one of the allowed values.");
- // In general, this API should be available when "bootctl_settings_toggle" is set, which
- // signals that there is a control for MTE in the user settings and this API fundamentally
- // is a way for the device admin to override that setting.
- // Allow bootctl_device_policy_manager as an override, e.g. to offer the
- // DevicePolicyManager only without a visible user setting.
- if (!mInjector.systemPropertiesGetBoolean(
- "ro.arm64.memtag.bootctl_device_policy_manager",
- mInjector.systemPropertiesGetBoolean(
- "ro.arm64.memtag.bootctl_settings_toggle", false))) {
- throw new UnsupportedOperationException("device does not support MTE");
- }
+ checkMteSupportedAndAllowedPolicy(flags);
final CallerIdentity caller = getCallerIdentity(callerPackageName);
// For now we continue to restrict the DISABLED setting to device owner - we might need
// another permission for this in future.
@@ -23783,6 +23766,53 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
@Override
+ public void setMtePolicyBySystem(
+ @NonNull String systemEntity, int policy) {
+ Objects.requireNonNull(systemEntity);
+ checkMteSupportedAndAllowedPolicy(policy);
+
+ Preconditions.checkCallAuthorization(isSystemUid(getCallerIdentity()),
+ "Only system services can call setMtePolicyBySystem");
+
+ if (!Flags.setMtePolicyCoexistence()) {
+ throw new UnsupportedOperationException("System can not set MTE policy only");
+ }
+
+ EnforcingAdmin admin = EnforcingAdmin.createSystemEnforcingAdmin(systemEntity);
+ if (policy != DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY) {
+ mDevicePolicyEngine.setGlobalPolicy(
+ PolicyDefinition.MEMORY_TAGGING,
+ admin,
+ new IntegerPolicyValue(policy));
+ } else {
+ mDevicePolicyEngine.removeGlobalPolicy(
+ PolicyDefinition.MEMORY_TAGGING,
+ admin);
+ }
+ }
+
+ private void checkMteSupportedAndAllowedPolicy(int policy) {
+ final Set<Integer> allowedModes =
+ Set.of(
+ DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY,
+ DevicePolicyManager.MTE_DISABLED,
+ DevicePolicyManager.MTE_ENABLED);
+ Preconditions.checkArgument(
+ allowedModes.contains(policy), "Provided mode is not one of the allowed values.");
+ // In general, this API should be available when "bootctl_settings_toggle" is set, which
+ // signals that there is a control for MTE in the user settings and this API fundamentally
+ // is a way for the device admin to override that setting.
+ // Allow bootctl_device_policy_manager as an override, e.g. to offer the
+ // DevicePolicyManager only without a visible user setting.
+ if (!mInjector.systemPropertiesGetBoolean(
+ "ro.arm64.memtag.bootctl_device_policy_manager",
+ mInjector.systemPropertiesGetBoolean(
+ "ro.arm64.memtag.bootctl_settings_toggle", false))) {
+ throw new UnsupportedOperationException("device does not support MTE");
+ }
+ }
+
+ @Override
public int getMtePolicy(String callerPackageName) {
final CallerIdentity caller = getCallerIdentity(callerPackageName);
if (Flags.setMtePolicyCoexistence()) {
@@ -24161,6 +24191,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
String supervisionBackupId = "36.2.supervision-support";
boolean supervisionMigrated = maybeMigrateResetPasswordTokenLocked(supervisionBackupId);
supervisionMigrated |= maybeMigrateSuspendedPackagesLocked(supervisionBackupId);
+ supervisionMigrated |= maybeMigrateSetKeyguardDisabledFeatures(supervisionBackupId);
if (supervisionMigrated) {
Slogf.i(LOG_TAG, "Backup made: " + supervisionBackupId);
}
@@ -24174,6 +24205,38 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
// Additional migration steps should repeat the pattern above with a new backupId.
}
+ @GuardedBy("getLockObject()")
+ private boolean maybeMigrateSetKeyguardDisabledFeatures(String backupId) {
+ Slog.i(LOG_TAG, "Migrating set keyguard disabled features to policy engine");
+ if (!Flags.setKeyguardDisabledFeaturesCoexistence()) {
+ return false;
+ }
+ if (mOwners.isSetKeyguardDisabledFeaturesMigrated()) {
+ return false;
+ }
+ // Create backup if none exists
+ mDevicePolicyEngine.createBackup(backupId);
+ try {
+ iterateThroughDpcAdminsLocked((admin, enforcingAdmin) -> {
+ if (admin.disabledKeyguardFeatures == 0) {
+ return;
+ }
+ int userId = enforcingAdmin.getUserId();
+ mDevicePolicyEngine.setLocalPolicy(
+ PolicyDefinition.KEYGUARD_DISABLED_FEATURES,
+ enforcingAdmin,
+ new IntegerPolicyValue(admin.disabledKeyguardFeatures),
+ userId);
+ });
+ } catch (Exception e) {
+ Slog.wtf(LOG_TAG, "Failed to migrate set keyguard disabled to policy engine", e);
+ }
+
+ Slog.i(LOG_TAG, "Marking set keyguard disabled features migration complete");
+ mOwners.markSetKeyguardDisabledFeaturesMigrated();
+ return true;
+ }
+
private void migratePermissionGrantStatePolicies() {
Slogf.i(LOG_TAG, "Migrating PERMISSION_GRANT policy to device policy engine.");
for (UserInfo userInfo : mUserManager.getUsers()) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
index be4eea42f09e..1c75f2f39c80 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
@@ -695,6 +695,19 @@ class Owners {
}
}
+ void markSetKeyguardDisabledFeaturesMigrated() {
+ synchronized (mData) {
+ mData.mSetKeyguardDisabledFeaturesMigrated = true;
+ mData.writeDeviceOwner();
+ }
+ }
+
+ boolean isSetKeyguardDisabledFeaturesMigrated() {
+ synchronized (mData) {
+ return mData.mSetKeyguardDisabledFeaturesMigrated;
+ }
+ }
+
@GuardedBy("mData")
void pushToAppOpsLocked() {
if (!mSystemReady) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
index 1cae924a8cd1..caaf0964bb4e 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
@@ -95,6 +95,8 @@ class OwnersData {
"resetPasswordWithTokenMigrated";
private static final String ATTR_MEMORY_TAGGING_MIGRATED =
"memoryTaggingMigrated";
+ private static final String ATTR_SET_KEYGUARD_DISABLED_FEATURES_MIGRATED =
+ "setKeyguardDisabledFeaturesMigrated";
private static final String ATTR_MIGRATED_POST_UPGRADE = "migratedPostUpgrade";
@@ -129,6 +131,7 @@ class OwnersData {
boolean mSuspendedPackagesMigrated = false;
boolean mResetPasswordWithTokenMigrated = false;
boolean mMemoryTaggingMigrated = false;
+ boolean mSetKeyguardDisabledFeaturesMigrated = false;
boolean mPoliciesMigratedPostUpdate = false;
@@ -434,6 +437,10 @@ class OwnersData {
out.attributeBoolean(null, ATTR_MEMORY_TAGGING_MIGRATED,
mMemoryTaggingMigrated);
}
+ if (Flags.setKeyguardDisabledFeaturesCoexistence()) {
+ out.attributeBoolean(null, ATTR_SET_KEYGUARD_DISABLED_FEATURES_MIGRATED,
+ mSetKeyguardDisabledFeaturesMigrated);
+ }
out.endTag(null, TAG_POLICY_ENGINE_MIGRATION);
}
@@ -510,6 +517,10 @@ class OwnersData {
mMemoryTaggingMigrated = Flags.setMtePolicyCoexistence()
&& parser.getAttributeBoolean(null,
ATTR_MEMORY_TAGGING_MIGRATED, false);
+ mSetKeyguardDisabledFeaturesMigrated =
+ Flags.setKeyguardDisabledFeaturesCoexistence()
+ && parser.getAttributeBoolean(null,
+ ATTR_SET_KEYGUARD_DISABLED_FEATURES_MIGRATED, false);
break;
default:
Slog.e(TAG, "Unexpected tag: " + tag);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 82d49fcac305..112414e6c4df 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -422,6 +422,8 @@ public final class SystemServer implements Dumpable {
"com.android.server.wifi.aware.WifiAwareService";
private static final String WIFI_P2P_SERVICE_CLASS =
"com.android.server.wifi.p2p.WifiP2pService";
+ private static final String WIFI_USD_SERVICE_CLASS =
+ "com.android.server.wifi.usd.UsdService";
private static final String CONNECTIVITY_SERVICE_APEX_PATH =
"/apex/com.android.tethering/javalib/service-connectivity.jar";
private static final String CONNECTIVITY_SERVICE_INITIALIZER_CLASS =
@@ -2145,6 +2147,13 @@ public final class SystemServer implements Dumpable {
mSystemServiceManager.startServiceFromJar(
WIFI_SCANNING_SERVICE_CLASS, WIFI_APEX_SERVICE_JAR_PATH);
t.traceEnd();
+ // Start USD service
+ if (android.net.wifi.flags.Flags.usd()) {
+ t.traceBegin("StartUsd");
+ mSystemServiceManager.startServiceFromJar(
+ WIFI_USD_SERVICE_CLASS, WIFI_APEX_SERVICE_JAR_PATH);
+ t.traceEnd();
+ }
}
if (context.getPackageManager().hasSystemFeature(
diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
index e307e529a40b..228e32e98cc7 100644
--- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
+++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
@@ -70,6 +70,7 @@ public final class ProfcollectForwardingService extends SystemService {
private int mUsageSetting;
private boolean mUploadEnabled;
+ private static boolean sVerityEnforced;
private boolean mAdbActive;
private IProfCollectd mIProfcollect;
@@ -117,6 +118,13 @@ public final class ProfcollectForwardingService extends SystemService {
mUsageSetting = -1;
}
+ // Check verity, disable profile upload if not enforced.
+ final String verityMode = SystemProperties.get("ro.boot.veritymode");
+ sVerityEnforced = verityMode.equals("enforcing");
+ if (!sVerityEnforced) {
+ Log.d(LOG_TAG, "verity is not enforced: " + verityMode);
+ }
+
mUploadEnabled =
context.getResources().getBoolean(R.bool.config_profcollectReportUploaderEnabled);
@@ -373,6 +381,10 @@ public final class ProfcollectForwardingService extends SystemService {
Log.i(LOG_TAG, "Upload is not enabled.");
return;
}
+ if (!sVerityEnforced) {
+ Log.i(LOG_TAG, "Verity is not enforced.");
+ return;
+ }
Intent intent = new Intent()
.setPackage("com.android.shell")
.setAction("com.android.shell.action.PROFCOLLECT_UPLOAD")
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
index 3fdb53f5ab59..31f03704a756 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
@@ -289,6 +289,7 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag
AndroidPackage::getEmergencyInstaller,
AndroidPackage::isAllowCrossUidActivitySwitchFromBelow,
AndroidPackage::getIntentMatchingFlags,
+ AndroidPackage::getPageSizeAppCompatFlags,
)
override fun extraParams() = listOf(
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index 993569fb17fe..0d25426700a6 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -392,3 +392,10 @@ test_module_config {
],
include_filters: ["com.android.server.StorageManagerServiceTest"],
}
+
+test_module_config {
+ name: "FrameworksMockingServicesTests_service_batteryServiceTest",
+ base: "FrameworksMockingServicesTests",
+ test_suites: ["device-tests"],
+ include_filters: ["com.android.server.BatteryServiceTest"],
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/BatteryServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/BatteryServiceTest.java
index 5e2f80bf8311..1fbd53a27a4f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/BatteryServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/BatteryServiceTest.java
@@ -79,6 +79,8 @@ public class BatteryServiceTest {
private static final int UPDATED_BATTERY_HEALTH = 3;
private static final int CURRENT_CHARGE_COUNTER = 4680000;
private static final int UPDATED_CHARGE_COUNTER = 4218000;
+ private static final int CURRENT_MAX_CHARGING_CURRENT = 298125;
+ private static final int UPDATED_MAX_CHARGING_CURRENT = 398125;
private static final int HANDLER_IDLE_TIME_MS = 5000;
@Rule
public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
@@ -143,7 +145,7 @@ public class BatteryServiceTest {
@EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
public void onlyVoltageUpdated_lessThenOnePercent_broadcastNotSent() {
mBatteryService.update(createHealthInfo(VOLTAGE_LESS_THEN_ONE_PERCENT, CURRENT_BATTERY_TEMP,
- CURRENT_CHARGE_COUNTER, CURRENT_BATTERY_HEALTH));
+ CURRENT_CHARGE_COUNTER, CURRENT_BATTERY_HEALTH, CURRENT_MAX_CHARGING_CURRENT));
waitForHandlerToExecute();
@@ -156,7 +158,8 @@ public class BatteryServiceTest {
mBatteryService.update(
createHealthInfo(VOLTAGE_MORE_THEN_ONE_PERCENT, CURRENT_BATTERY_TEMP,
CURRENT_CHARGE_COUNTER,
- CURRENT_BATTERY_HEALTH));
+ CURRENT_BATTERY_HEALTH,
+ CURRENT_MAX_CHARGING_CURRENT));
waitForHandlerToExecute();
@@ -165,13 +168,17 @@ public class BatteryServiceTest {
@Test
@EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
- public void onlyVoltageUpdated_broadcastSent() {
+ public void voltageUpdated_withUpdateInChargingCurrent_broadcastSent() {
mBatteryService.mLastBroadcastVoltageUpdateTime = SystemClock.elapsedRealtime() - 20000;
+ long lastChargingCurrentUpdateTime =
+ mBatteryService.mLastBroadcastMaxChargingCurrentUpdateTime;
mBatteryService.update(createHealthInfo(VOLTAGE_MORE_THEN_ONE_PERCENT, CURRENT_BATTERY_TEMP,
- CURRENT_CHARGE_COUNTER, CURRENT_BATTERY_HEALTH));
+ CURRENT_CHARGE_COUNTER, CURRENT_BATTERY_HEALTH, UPDATED_MAX_CHARGING_CURRENT));
waitForHandlerToExecute();
+ assertTrue(lastChargingCurrentUpdateTime
+ < mBatteryService.mLastBroadcastMaxChargingCurrentUpdateTime);
verifyNumberOfTimesBroadcastSent(1);
}
@@ -180,7 +187,8 @@ public class BatteryServiceTest {
public void onlyTempUpdated_lessThenOneDegreeCelsius_broadcastNotSent() {
mBatteryService.update(
createHealthInfo(CURRENT_BATTERY_VOLTAGE, TEMP_LESS_THEN_ONE_DEGREE_CELSIUS,
- CURRENT_CHARGE_COUNTER, CURRENT_BATTERY_HEALTH));
+ CURRENT_CHARGE_COUNTER, CURRENT_BATTERY_HEALTH,
+ CURRENT_MAX_CHARGING_CURRENT));
waitForHandlerToExecute();
@@ -191,23 +199,31 @@ public class BatteryServiceTest {
@EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
public void tempUpdated_broadcastSent() {
long lastVoltageUpdateTime = mBatteryService.mLastBroadcastVoltageUpdateTime;
+ long lastChargingCurrentUpdateTime =
+ mBatteryService.mLastBroadcastMaxChargingCurrentUpdateTime;
mBatteryService.update(
createHealthInfo(VOLTAGE_LESS_THEN_ONE_PERCENT, TEMP_MORE_THEN_ONE_DEGREE_CELSIUS,
- UPDATED_CHARGE_COUNTER, CURRENT_BATTERY_HEALTH));
+ UPDATED_CHARGE_COUNTER, CURRENT_BATTERY_HEALTH,
+ UPDATED_MAX_CHARGING_CURRENT));
waitForHandlerToExecute();
assertTrue(lastVoltageUpdateTime < mBatteryService.mLastBroadcastVoltageUpdateTime);
+ assertTrue(lastChargingCurrentUpdateTime
+ < mBatteryService.mLastBroadcastMaxChargingCurrentUpdateTime);
verifyNumberOfTimesBroadcastSent(1);
}
@Test
@EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
- public void batteryHealthUpdated_voltageAndTempConst_broadcastSent() {
+ public void batteryHealthUpdated_withOtherExtrasConstant_broadcastSent() {
+ long lastVoltageUpdateTime = mBatteryService.mLastBroadcastVoltageUpdateTime;
+ long lastChargingCurrentUpdateTime =
+ mBatteryService.mLastBroadcastMaxChargingCurrentUpdateTime;
mBatteryService.update(
- createHealthInfo(CURRENT_BATTERY_VOLTAGE, CURRENT_BATTERY_TEMP,
+ createHealthInfo(VOLTAGE_LESS_THEN_ONE_PERCENT, CURRENT_BATTERY_TEMP,
CURRENT_CHARGE_COUNTER,
- UPDATED_BATTERY_HEALTH));
+ UPDATED_BATTERY_HEALTH, UPDATED_MAX_CHARGING_CURRENT));
waitForHandlerToExecute();
@@ -217,10 +233,13 @@ public class BatteryServiceTest {
mBatteryService.update(
createHealthInfo(CURRENT_BATTERY_VOLTAGE, CURRENT_BATTERY_TEMP,
UPDATED_CHARGE_COUNTER,
- UPDATED_BATTERY_HEALTH));
+ UPDATED_BATTERY_HEALTH, CURRENT_MAX_CHARGING_CURRENT));
waitForHandlerToExecute();
+ assertTrue(lastVoltageUpdateTime < mBatteryService.mLastBroadcastVoltageUpdateTime);
+ assertTrue(lastChargingCurrentUpdateTime
+ < mBatteryService.mLastBroadcastMaxChargingCurrentUpdateTime);
verifyNumberOfTimesBroadcastSent(1);
}
@@ -228,7 +247,7 @@ public class BatteryServiceTest {
@DisableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
public void voltageUpdated_lessThanOnePercent_flagDisabled_broadcastSent() {
mBatteryService.update(createHealthInfo(VOLTAGE_LESS_THEN_ONE_PERCENT, CURRENT_BATTERY_TEMP,
- CURRENT_CHARGE_COUNTER, CURRENT_BATTERY_HEALTH));
+ CURRENT_CHARGE_COUNTER, CURRENT_BATTERY_HEALTH, CURRENT_MAX_CHARGING_CURRENT));
waitForHandlerToExecute();
@@ -241,7 +260,7 @@ public class BatteryServiceTest {
mBatteryService.update(
createHealthInfo(CURRENT_BATTERY_VOLTAGE, CURRENT_BATTERY_TEMP,
UPDATED_CHARGE_COUNTER,
- CURRENT_BATTERY_HEALTH));
+ CURRENT_BATTERY_HEALTH, CURRENT_MAX_CHARGING_CURRENT));
waitForHandlerToExecute();
@@ -254,7 +273,7 @@ public class BatteryServiceTest {
mBatteryService.update(
createHealthInfo(CURRENT_BATTERY_VOLTAGE, TEMP_LESS_THEN_ONE_DEGREE_CELSIUS,
UPDATED_CHARGE_COUNTER,
- CURRENT_BATTERY_HEALTH));
+ CURRENT_BATTERY_HEALTH, CURRENT_MAX_CHARGING_CURRENT));
waitForHandlerToExecute();
@@ -267,10 +286,42 @@ public class BatteryServiceTest {
mBatteryService.update(
createHealthInfo(CURRENT_BATTERY_VOLTAGE, CURRENT_BATTERY_TEMP,
UPDATED_CHARGE_COUNTER,
- CURRENT_BATTERY_HEALTH));
+ CURRENT_BATTERY_HEALTH, CURRENT_MAX_CHARGING_CURRENT));
+
+ waitForHandlerToExecute();
+
+ verifyNumberOfTimesBroadcastSent(1);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
+ public void onlyMaxChargingCurrentUpdated_beforeFiveSeconds_broadcastNotSent() {
+ mBatteryService.update(
+ createHealthInfo(CURRENT_BATTERY_VOLTAGE, CURRENT_BATTERY_TEMP,
+ CURRENT_CHARGE_COUNTER,
+ CURRENT_BATTERY_HEALTH,
+ UPDATED_MAX_CHARGING_CURRENT));
waitForHandlerToExecute();
+ verifyNumberOfTimesBroadcastSent(0);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
+ public void maxChargingCurrentUpdated_afterFiveSeconds_broadcastSent() {
+ mBatteryService.mLastBroadcastMaxChargingCurrentUpdateTime =
+ SystemClock.elapsedRealtime() - 5000;
+ long lastVoltageUpdateTime = mBatteryService.mLastBroadcastVoltageUpdateTime;
+ mBatteryService.update(
+ createHealthInfo(VOLTAGE_MORE_THEN_ONE_PERCENT, CURRENT_BATTERY_TEMP,
+ CURRENT_CHARGE_COUNTER,
+ CURRENT_BATTERY_HEALTH,
+ UPDATED_MAX_CHARGING_CURRENT));
+
+ waitForHandlerToExecute();
+
+ assertTrue(lastVoltageUpdateTime < mBatteryService.mLastBroadcastVoltageUpdateTime);
verifyNumberOfTimesBroadcastSent(1);
}
@@ -278,7 +329,8 @@ public class BatteryServiceTest {
int batteryVoltage,
int batteryTemperature,
int batteryChargeCounter,
- int batteryHealth) {
+ int batteryHealth,
+ int maxChargingCurrent) {
HealthInfo h = new HealthInfo();
h.batteryVoltageMillivolts = batteryVoltage;
h.batteryTemperatureTenthsCelsius = batteryTemperature;
@@ -287,7 +339,7 @@ public class BatteryServiceTest {
h.batteryHealth = batteryHealth;
h.batteryPresent = true;
h.batteryLevel = 100;
- h.maxChargingCurrentMicroamps = 298125;
+ h.maxChargingCurrentMicroamps = maxChargingCurrent;
h.batteryCurrentAverageMicroamps = -2812;
h.batteryCurrentMicroamps = 298125;
h.maxChargingVoltageMicrovolts = 3000;
@@ -308,7 +360,8 @@ public class BatteryServiceTest {
mBatteryService.update(
createHealthInfo(CURRENT_BATTERY_VOLTAGE, CURRENT_BATTERY_TEMP,
CURRENT_CHARGE_COUNTER,
- CURRENT_BATTERY_HEALTH));
+ CURRENT_BATTERY_HEALTH,
+ CURRENT_MAX_CHARGING_CURRENT));
waitForHandlerToExecute();
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java b/services/tests/mockingservicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java
index c77ab0f303d9..745424836faa 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java
@@ -21,10 +21,14 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyString;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static org.mockito.Mockito.verify;
+
import android.content.ContentResolver;
import android.os.SystemProperties;
+import android.provider.DeviceConfig;
import android.provider.Settings;
import android.text.TextUtils;
+import android.util.proto.ProtoOutputStream;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
@@ -34,6 +38,7 @@ import org.junit.Before;
import org.junit.Test;
import org.mockito.Answers;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
import org.mockito.stubbing.Answer;
@@ -111,6 +116,20 @@ public class SettingsToPropertiesMapperTest {
}
@Test
+ public void testClearAconfigStorageOverride() {
+ SettingsToPropertiesMapper spyMapper = Mockito.spy(new SettingsToPropertiesMapper(
+ mMockContentResolver, TEST_MAPPING, new String[] {}, new String[] {}));
+ HashMap flags = new HashMap();
+ flags.put("test_package.test_flag", null);
+ DeviceConfig.Properties props = new DeviceConfig.Properties("test_namespace", flags);
+
+ spyMapper.setLocalOverridesInNewStorage(props);
+
+ verify(spyMapper).writeFlagOverrideRemovalRequest(new ProtoOutputStream(),
+ "test_package", "test_flag", true);
+ }
+
+ @Test
public void validateRegisteredGlobalSettings() {
HashSet<String> hashSet = new HashSet<>();
for (String globalSetting : SettingsToPropertiesMapper.sGlobalSettings) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/BackupAgentConnectionManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/BackupAgentConnectionManagerTest.java
index 19e43b6fa851..2461e9e79acf 100644
--- a/services/tests/mockingservicestests/src/com/android/server/backup/BackupAgentConnectionManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/BackupAgentConnectionManagerTest.java
@@ -196,6 +196,31 @@ public class BackupAgentConnectionManagerTest {
}
@Test
+ public void bindToAgentSynchronous_unexpectedAgentConnected_doesNotReturnWrongAgent()
+ throws Exception {
+ when(mActivityManager.bindBackupAgent(eq(TEST_PACKAGE), anyInt(), anyInt(),
+ anyInt(), anyBoolean())).thenReturn(true);
+ // This is so that IBackupAgent.Stub.asInterface() works.
+ when(mBackupAgentStub.queryLocalInterface(any())).thenReturn(mBackupAgentStub);
+ when(mConnectionManager.getCallingUid()).thenReturn(Process.SYSTEM_UID);
+
+ // This is going to block until it receives the callback so we need to run it on a
+ // separate thread.
+ Thread testThread = new Thread(() -> setBackupAgentResult(
+ mConnectionManager.bindToAgentSynchronous(mTestApplicationInfo,
+ ApplicationThreadConstants.BACKUP_MODE_FULL, BackupDestination.CLOUD)),
+ "backup-agent-connection-manager-test");
+ testThread.start();
+ // Give the testThread a head start, otherwise agentConnected() might run before
+ // bindToAgentSynchronous() is called.
+ Thread.sleep(500);
+ mConnectionManager.agentConnected("com.other.package", mBackupAgentStub);
+ testThread.join(100); // Avoid waiting the full timeout.
+
+ assertThat(mBackupAgentResult).isNull();
+ }
+
+ @Test
@DisableFlags(Flags.FLAG_ENABLE_RESTRICTED_MODE_CHANGES)
public void bindToAgentSynchronous_restrictedModeChangesFlagOff_shouldUseRestrictedMode()
throws Exception {
@@ -345,6 +370,7 @@ public class BackupAgentConnectionManagerTest {
@Test
public void agentDisconnected_cancelsCurrentOperations() throws Exception {
+ when(mConnectionManager.getCallingUid()).thenReturn(Process.SYSTEM_UID);
when(mOperationStorage.operationTokensForPackage(eq(TEST_PACKAGE))).thenReturn(
ImmutableSet.of(123, 456, 789));
when(mConnectionManager.getThreadForCancellation(any())).thenAnswer(invocation -> {
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
index dd7ce21e3628..c831475577d8 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
@@ -16,6 +16,7 @@
package com.android.server.job;
+import static android.app.job.Flags.FLAG_HANDLE_ABANDONED_JOBS;
import static android.text.format.DateUtils.DAY_IN_MILLIS;
import static android.text.format.DateUtils.HOUR_IN_MILLIS;
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
@@ -82,6 +83,7 @@ import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.WorkSource;
import android.os.WorkSource.WorkChain;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.RequiresFlagsDisabled;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
@@ -1056,6 +1058,75 @@ public class JobSchedulerServiceTest {
/**
* Confirm that
* {@link JobSchedulerService#getRescheduleJobForFailureLocked(JobStatus, int, int)}
+ * returns a job with the correct delay for abandoned jobs.
+ */
+ @Test
+ @EnableFlags(FLAG_HANDLE_ABANDONED_JOBS)
+ public void testGetRescheduleJobForFailure_abandonedJob() {
+ final long nowElapsed = sElapsedRealtimeClock.millis();
+ final long initialBackoffMs = MINUTE_IN_MILLIS;
+ mService.mConstants.SYSTEM_STOP_TO_FAILURE_RATIO = 3;
+
+ JobStatus originalJob = createJobStatus("testGetRescheduleJobForFailure",
+ createJobInfo()
+ .setBackoffCriteria(initialBackoffMs, JobInfo.BACKOFF_POLICY_LINEAR));
+ assertEquals(JobStatus.NO_EARLIEST_RUNTIME, originalJob.getEarliestRunTime());
+ assertEquals(JobStatus.NO_LATEST_RUNTIME, originalJob.getLatestRunTimeElapsed());
+
+ // failure = 1, systemStop = 0, abandoned = 1
+ JobStatus rescheduledJob = mService.getRescheduleJobForFailureLocked(originalJob,
+ JobParameters.STOP_REASON_DEVICE_STATE,
+ JobParameters.INTERNAL_STOP_REASON_TIMEOUT_ABANDONED);
+ assertEquals(nowElapsed + initialBackoffMs, rescheduledJob.getEarliestRunTime());
+ assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed());
+
+ // failure = 2, systemstop = 0, abandoned = 2
+ rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob,
+ JobParameters.STOP_REASON_DEVICE_STATE,
+ JobParameters.INTERNAL_STOP_REASON_TIMEOUT_ABANDONED);
+ assertEquals(nowElapsed + (2 * initialBackoffMs), rescheduledJob.getEarliestRunTime());
+ assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed());
+
+ // failure = 3, systemstop = 0, abandoned = 3
+ rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob,
+ JobParameters.STOP_REASON_DEVICE_STATE,
+ JobParameters.INTERNAL_STOP_REASON_TIMEOUT_ABANDONED);
+ assertEquals(nowElapsed + (3 * initialBackoffMs), rescheduledJob.getEarliestRunTime());
+ assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed());
+
+ // failure = 4, systemstop = 0, abandoned = 4
+ rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob,
+ JobParameters.STOP_REASON_DEVICE_STATE,
+ JobParameters.INTERNAL_STOP_REASON_TIMEOUT_ABANDONED);
+ assertEquals(
+ nowElapsed + ((long) Math.scalb((float) initialBackoffMs, 3)),
+ rescheduledJob.getEarliestRunTime());
+ assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed());
+
+ // failure = 4, systemstop = 1, abandoned = 4
+ rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob,
+ JobParameters.STOP_REASON_DEVICE_STATE,
+ JobParameters.INTERNAL_STOP_REASON_DEVICE_THERMAL);
+ assertEquals(
+ nowElapsed + ((long) Math.scalb((float) initialBackoffMs, 3)),
+ rescheduledJob.getEarliestRunTime());
+ assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed());
+
+ // failure = 4, systemStop = 4 / SYSTEM_STOP_TO_FAILURE_RATIO, abandoned = 4
+ for (int i = 0; i < mService.mConstants.SYSTEM_STOP_TO_FAILURE_RATIO; ++i) {
+ rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob,
+ JobParameters.STOP_REASON_SYSTEM_PROCESSING,
+ JobParameters.INTERNAL_STOP_REASON_RTC_UPDATED);
+ }
+ assertEquals(
+ nowElapsed + ((long) Math.scalb((float) initialBackoffMs, 4)),
+ rescheduledJob.getEarliestRunTime());
+ assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed());
+ }
+
+ /**
+ * Confirm that
+ * {@link JobSchedulerService#getRescheduleJobForFailureLocked(JobStatus, int, int)}
* returns a job that is correctly marked as demoted by the user.
*/
@Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
index c6a6865f1cf1..c64973a67589 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
@@ -706,14 +706,14 @@ public class FlexibilityControllerTest {
// "True" start is nowElapsed + HOUR_IN_MILLIS
nowElapsed + HOUR_IN_MILLIS + adjustmentMs,
nowElapsed + 2 * HOUR_IN_MILLIS,
- 0 /* numFailures */, 0 /* numSystemStops */,
+ 0 /* numFailures */, 0 /* numAbandonedFailures */, 0 /* numSystemStops */,
JobSchedulerService.sSystemClock.millis() /* lastSuccessfulRunTime */,
0, 0);
jsFlex = new JobStatus(jsFlex,
// "True" start is nowElapsed + 2 * HOUR_IN_MILLIS - 20 * MINUTE_IN_MILLIS
nowElapsed + 2 * HOUR_IN_MILLIS - 20 * MINUTE_IN_MILLIS + adjustmentMs,
nowElapsed + 2 * HOUR_IN_MILLIS,
- 0 /* numFailures */, 0 /* numSystemStops */,
+ 0 /* numFailures */, 0 /* numAbandonedFailures */, 0 /* numSystemStops */,
JobSchedulerService.sSystemClock.millis() /* lastSuccessfulRunTime */,
0, 0);
@@ -726,13 +726,13 @@ public class FlexibilityControllerTest {
jsBasic = new JobStatus(jsBasic,
nowElapsed + 30 * MINUTE_IN_MILLIS,
NO_LATEST_RUNTIME,
- 1 /* numFailures */, 1 /* numSystemStops */,
+ 1 /* numFailures */, 0 /* numAbandonedFailures */, 1 /* numSystemStops */,
JobSchedulerService.sSystemClock.millis() /* lastSuccessfulRunTime */,
0, 0);
jsFlex = new JobStatus(jsFlex,
nowElapsed + 30 * MINUTE_IN_MILLIS,
NO_LATEST_RUNTIME,
- 1 /* numFailures */, 1 /* numSystemStops */,
+ 1 /* numFailures */, 0 /* numAbandonedFailures */, 1 /* numSystemStops */,
JobSchedulerService.sSystemClock.millis() /* lastSuccessfulRunTime */,
0, 0);
@@ -847,21 +847,24 @@ public class FlexibilityControllerTest {
JobInfo.Builder jb = createJob(0).setOverrideDeadline(HOUR_IN_MILLIS);
JobStatus js = createJobStatus("time", jb);
js = new JobStatus(
- js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 2, /* numSystemStops */ 0,
+ js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 2,
+ 0 /* numAbandonedFailures */, /* numSystemStops */ 0,
0, FROZEN_TIME, FROZEN_TIME);
assertEquals(mFcConfig.RESCHEDULED_JOB_DEADLINE_MS,
mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 0));
js = new JobStatus(
- js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 2, /* numSystemStops */ 1,
+ js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 2,
+ 0 /* numAbandonedFailures */, /* numSystemStops */ 1,
0, FROZEN_TIME, FROZEN_TIME);
assertEquals(2 * mFcConfig.RESCHEDULED_JOB_DEADLINE_MS,
mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 0));
js = new JobStatus(
- js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 0, /* numSystemStops */ 10,
+ js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 0,
+ 0 /* numAbandonedFailures */, /* numSystemStops */ 10,
0, FROZEN_TIME, FROZEN_TIME);
assertEquals(mFcConfig.MAX_RESCHEDULED_DEADLINE_MS,
mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 0));
@@ -1092,11 +1095,13 @@ public class FlexibilityControllerTest {
JobInfo.Builder jb = createJob(0);
JobStatus js = createJobStatus("time", jb);
js = new JobStatus(
- js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 1, /* numSystemStops */ 0,
+ js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 1,
+ /* numAbandonedFailures */ 0, /* numSystemStops */ 0,
0, FROZEN_TIME, FROZEN_TIME);
assertFalse(js.hasFlexibilityConstraint());
js = new JobStatus(
- js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 0, /* numSystemStops */ 1,
+ js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 0,
+ /* numAbandonedFailures */ 0, /* numSystemStops */ 1,
0, FROZEN_TIME, FROZEN_TIME);
assertFalse(js.hasFlexibilityConstraint());
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
index 2d0f4b69e2fe..86101cf591e7 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
@@ -459,35 +459,35 @@ public class JobStatusTest {
int numFailures = 1;
int numSystemStops = 0;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_MAX, job.getEffectivePriority());
// 2+ failures, priority should be lowered as much as possible.
numFailures = 2;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_HIGH, job.getEffectivePriority());
numFailures = 5;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_HIGH, job.getEffectivePriority());
numFailures = 8;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_HIGH, job.getEffectivePriority());
// System stops shouldn't factor in the downgrade.
numSystemStops = 10;
numFailures = 0;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_MAX, job.getEffectivePriority());
// Less than 2 failures, but job is downgraded.
numFailures = 1;
numSystemStops = 0;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
job.addInternalFlags(JobStatus.INTERNAL_FLAG_DEMOTED_BY_USER);
assertEquals(JobInfo.PRIORITY_HIGH, job.getEffectivePriority());
}
@@ -505,44 +505,44 @@ public class JobStatusTest {
int numFailures = 1;
int numSystemStops = 0;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_HIGH, job.getEffectivePriority());
// Failures in [2,4), priority should be lowered slightly.
numFailures = 2;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_DEFAULT, job.getEffectivePriority());
numFailures = 3;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_DEFAULT, job.getEffectivePriority());
// Failures in [4,6), priority should be lowered more.
numFailures = 4;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_LOW, job.getEffectivePriority());
numFailures = 5;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_LOW, job.getEffectivePriority());
// 6+ failures, priority should be lowered as much as possible.
numFailures = 6;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_MIN, job.getEffectivePriority());
numFailures = 12;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_MIN, job.getEffectivePriority());
// System stops shouldn't factor in the downgrade.
numSystemStops = 10;
numFailures = 0;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_HIGH, job.getEffectivePriority());
}
@@ -563,32 +563,32 @@ public class JobStatusTest {
int numFailures = 1;
int numSystemStops = 0;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_LOW, job.getEffectivePriority());
numFailures = 4;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_LOW, job.getEffectivePriority());
numFailures = 5;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_LOW, job.getEffectivePriority());
// 6+ failures, priority should be lowered as much as possible.
numFailures = 6;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_MIN, job.getEffectivePriority());
numFailures = 12;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_MIN, job.getEffectivePriority());
// System stops shouldn't factor in the downgrade.
numSystemStops = 10;
numFailures = 0;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_LOW, job.getEffectivePriority());
}
@@ -606,28 +606,28 @@ public class JobStatusTest {
int numFailures = 1;
int numSystemStops = 0;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_MAX, job.getEffectivePriority());
// 2+ failures, priority shouldn't be affected while job is still a UI job
numFailures = 2;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_MAX, job.getEffectivePriority());
numFailures = 5;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_MAX, job.getEffectivePriority());
numFailures = 8;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_MAX, job.getEffectivePriority());
// System stops shouldn't factor in the downgrade.
numSystemStops = 10;
numFailures = 0;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_MAX, job.getEffectivePriority());
// Job can no long run as user-initiated. Downgrades should be effective.
@@ -641,28 +641,28 @@ public class JobStatusTest {
numFailures = 1;
numSystemStops = 0;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_HIGH, job.getEffectivePriority());
// 2+ failures, priority should start getting lower
numFailures = 2;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_DEFAULT, job.getEffectivePriority());
numFailures = 5;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_LOW, job.getEffectivePriority());
numFailures = 8;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_MIN, job.getEffectivePriority());
// System stops shouldn't factor in the downgrade.
numSystemStops = 10;
numFailures = 0;
job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
- numSystemStops, 0, 0, 0);
+ 0, numSystemStops, 0, 0, 0);
assertEquals(JobInfo.PRIORITY_HIGH, job.getEffectivePriority());
}
@@ -772,14 +772,14 @@ public class JobStatusTest {
assertTrue(job.shouldTreatAsUserInitiatedJob());
JobStatus rescheduledJob = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME,
- 0, 0, 0, 0, 0);
+ 0, 0, 0, 0, 0, 0);
assertTrue(rescheduledJob.shouldTreatAsUserInitiatedJob());
job.addInternalFlags(JobStatus.INTERNAL_FLAG_DEMOTED_BY_USER);
assertFalse(job.shouldTreatAsUserInitiatedJob());
rescheduledJob = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME,
- 0, 0, 0, 0, 0);
+ 0, 0, 0, 0, 0, 0);
assertFalse(rescheduledJob.shouldTreatAsUserInitiatedJob());
rescheduledJob.removeInternalFlags(JobStatus.INTERNAL_FLAG_DEMOTED_BY_USER);
@@ -797,14 +797,14 @@ public class JobStatusTest {
assertTrue(job.shouldTreatAsUserInitiatedJob());
JobStatus rescheduledJob = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME,
- 0, 0, 0, 0, 0);
+ 0, 0, 0, 0, 0, 0);
assertTrue(rescheduledJob.shouldTreatAsUserInitiatedJob());
job.addInternalFlags(JobStatus.INTERNAL_FLAG_DEMOTED_BY_SYSTEM_UIJ);
assertFalse(job.shouldTreatAsUserInitiatedJob());
rescheduledJob = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME,
- 0, 0, 0, 0, 0);
+ 0, 0, 0, 0, 0, 0);
assertFalse(rescheduledJob.shouldTreatAsUserInitiatedJob());
rescheduledJob.removeInternalFlags(JobStatus.INTERNAL_FLAG_DEMOTED_BY_SYSTEM_UIJ);
diff --git a/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java b/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java
index e631cb66eaf7..313c01d5ce58 100644
--- a/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java
+++ b/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java
@@ -18,7 +18,6 @@ package com.android.server.power.hint;
import static com.android.server.power.hint.HintManagerService.CLEAN_UP_UID_DELAY_MILLIS;
-import static com.android.server.power.hint.HintManagerService.DEFAULT_HEADROOM_PID;
import static com.google.common.truth.Truth.assertThat;
@@ -53,7 +52,9 @@ import android.hardware.common.fmq.MQDescriptor;
import android.hardware.power.ChannelConfig;
import android.hardware.power.ChannelMessage;
import android.hardware.power.CpuHeadroomParams;
+import android.hardware.power.CpuHeadroomResult;
import android.hardware.power.GpuHeadroomParams;
+import android.hardware.power.GpuHeadroomResult;
import android.hardware.power.IPower;
import android.hardware.power.SessionConfig;
import android.hardware.power.SessionTag;
@@ -1251,67 +1252,98 @@ public class HintManagerServiceTest {
CpuHeadroomParams halParams1 = new CpuHeadroomParams();
halParams1.calculationType = CpuHeadroomParams.CalculationType.MIN;
halParams1.selectionType = CpuHeadroomParams.SelectionType.ALL;
- halParams1.pid = Process.myPid();
+ halParams1.tids = new int[]{Process.myPid()};
CpuHeadroomParamsInternal params2 = new CpuHeadroomParamsInternal();
params2.usesDeviceHeadroom = true;
- params2.calculationType = CpuHeadroomParams.CalculationType.AVERAGE;
+ params2.calculationType = CpuHeadroomParams.CalculationType.MIN;
params2.selectionType = CpuHeadroomParams.SelectionType.PER_CORE;
CpuHeadroomParams halParams2 = new CpuHeadroomParams();
- halParams2.calculationType = CpuHeadroomParams.CalculationType.AVERAGE;
+ halParams2.calculationType = CpuHeadroomParams.CalculationType.MIN;
halParams2.selectionType = CpuHeadroomParams.SelectionType.PER_CORE;
- halParams2.pid = DEFAULT_HEADROOM_PID;
+ halParams2.tids = new int[]{};
+
+ CpuHeadroomParamsInternal params3 = new CpuHeadroomParamsInternal();
+ params3.calculationType = CpuHeadroomParams.CalculationType.AVERAGE;
+ params3.selectionType = CpuHeadroomParams.SelectionType.ALL;
+ CpuHeadroomParams halParams3 = new CpuHeadroomParams();
+ halParams3.calculationType = CpuHeadroomParams.CalculationType.AVERAGE;
+ halParams3.selectionType = CpuHeadroomParams.SelectionType.ALL;
+ halParams3.tids = new int[]{Process.myPid()};
+
+ // this params should not be cached as the window is not default
+ CpuHeadroomParamsInternal params4 = new CpuHeadroomParamsInternal();
+ params4.calculationWindowMillis = 123;
+ CpuHeadroomParams halParams4 = new CpuHeadroomParams();
+ halParams4.calculationType = CpuHeadroomParams.CalculationType.MIN;
+ halParams4.selectionType = CpuHeadroomParams.SelectionType.ALL;
+ halParams4.calculationWindowMillis = 123;
+ halParams4.tids = new int[]{Process.myPid()};
- float[] headroom1 = new float[] {0.1f};
- when(mIPowerMock.getCpuHeadroom(eq(halParams1))).thenReturn(headroom1);
- float[] headroom2 = new float[] {0.1f, 0.5f};
- when(mIPowerMock.getCpuHeadroom(eq(halParams2))).thenReturn(headroom2);
+ float headroom1 = 0.1f;
+ CpuHeadroomResult halRet1 = CpuHeadroomResult.globalHeadroom(headroom1);
+ when(mIPowerMock.getCpuHeadroom(eq(halParams1))).thenReturn(halRet1);
+ float[] headroom2 = new float[] {0.2f, 0.2f};
+ CpuHeadroomResult halRet2 = CpuHeadroomResult.perCoreHeadroom(headroom2);
+ when(mIPowerMock.getCpuHeadroom(eq(halParams2))).thenReturn(halRet2);
+ float headroom3 = 0.3f;
+ CpuHeadroomResult halRet3 = CpuHeadroomResult.globalHeadroom(headroom3);
+ when(mIPowerMock.getCpuHeadroom(eq(halParams3))).thenReturn(halRet3);
+ float headroom4 = 0.4f;
+ CpuHeadroomResult halRet4 = CpuHeadroomResult.globalHeadroom(headroom4);
+ when(mIPowerMock.getCpuHeadroom(eq(halParams4))).thenReturn(halRet4);
HintManagerService service = createService();
clearInvocations(mIPowerMock);
service.getBinderServiceInstance().getCpuHeadroomMinIntervalMillis();
verify(mIPowerMock, times(0)).getCpuHeadroomMinIntervalMillis();
- service.getBinderServiceInstance().getCpuHeadroom(params1);
+ assertEquals(halRet1, service.getBinderServiceInstance().getCpuHeadroom(params1));
verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams1));
- service.getBinderServiceInstance().getCpuHeadroom(params2);
+ assertEquals(halRet2, service.getBinderServiceInstance().getCpuHeadroom(params2));
verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams2));
+ assertEquals(halRet3, service.getBinderServiceInstance().getCpuHeadroom(params3));
+ verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams3));
+ assertEquals(halRet4, service.getBinderServiceInstance().getCpuHeadroom(params4));
+ verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams4));
// verify cache is working
clearInvocations(mIPowerMock);
- assertArrayEquals(headroom1, service.getBinderServiceInstance().getCpuHeadroom(params1),
- 0.01f);
- assertArrayEquals(headroom2, service.getBinderServiceInstance().getCpuHeadroom(params2),
- 0.01f);
- verify(mIPowerMock, times(0)).getCpuHeadroom(any());
+ assertEquals(halRet1, service.getBinderServiceInstance().getCpuHeadroom(params1));
+ assertEquals(halRet2, service.getBinderServiceInstance().getCpuHeadroom(params2));
+ assertEquals(halRet3, service.getBinderServiceInstance().getCpuHeadroom(params3));
+ assertEquals(halRet4, service.getBinderServiceInstance().getCpuHeadroom(params4));
+ verify(mIPowerMock, times(1)).getCpuHeadroom(any());
+ verify(mIPowerMock, times(0)).getCpuHeadroom(eq(halParams1));
+ verify(mIPowerMock, times(0)).getCpuHeadroom(eq(halParams2));
+ verify(mIPowerMock, times(0)).getCpuHeadroom(eq(halParams3));
+ verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams4));
// after 1 more second it should be served with cache still
Thread.sleep(1000);
clearInvocations(mIPowerMock);
- assertArrayEquals(headroom1, service.getBinderServiceInstance().getCpuHeadroom(params1),
- 0.01f);
- assertArrayEquals(headroom2, service.getBinderServiceInstance().getCpuHeadroom(params2),
- 0.01f);
- verify(mIPowerMock, times(0)).getCpuHeadroom(any());
-
- // after 1.5 more second it should be served with cache still as timer reset
- Thread.sleep(1500);
- clearInvocations(mIPowerMock);
- assertArrayEquals(headroom1, service.getBinderServiceInstance().getCpuHeadroom(params1),
- 0.01f);
- assertArrayEquals(headroom2, service.getBinderServiceInstance().getCpuHeadroom(params2),
- 0.01f);
- verify(mIPowerMock, times(0)).getCpuHeadroom(any());
+ assertEquals(halRet1, service.getBinderServiceInstance().getCpuHeadroom(params1));
+ assertEquals(halRet2, service.getBinderServiceInstance().getCpuHeadroom(params2));
+ assertEquals(halRet3, service.getBinderServiceInstance().getCpuHeadroom(params3));
+ assertEquals(halRet4, service.getBinderServiceInstance().getCpuHeadroom(params4));
+ verify(mIPowerMock, times(1)).getCpuHeadroom(any());
+ verify(mIPowerMock, times(0)).getCpuHeadroom(eq(halParams1));
+ verify(mIPowerMock, times(0)).getCpuHeadroom(eq(halParams2));
+ verify(mIPowerMock, times(0)).getCpuHeadroom(eq(halParams3));
+ verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams4));
// after 2+ seconds it should be served from HAL as it exceeds 2000 millis interval
- Thread.sleep(2100);
+ Thread.sleep(1100);
clearInvocations(mIPowerMock);
- assertArrayEquals(headroom1, service.getBinderServiceInstance().getCpuHeadroom(params1),
- 0.01f);
- assertArrayEquals(headroom2, service.getBinderServiceInstance().getCpuHeadroom(params2),
- 0.01f);
+ assertEquals(halRet1, service.getBinderServiceInstance().getCpuHeadroom(params1));
+ assertEquals(halRet2, service.getBinderServiceInstance().getCpuHeadroom(params2));
+ assertEquals(halRet3, service.getBinderServiceInstance().getCpuHeadroom(params3));
+ assertEquals(halRet4, service.getBinderServiceInstance().getCpuHeadroom(params4));
+ verify(mIPowerMock, times(4)).getCpuHeadroom(any());
verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams1));
verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams2));
+ verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams3));
+ verify(mIPowerMock, times(1)).getCpuHeadroom(eq(halParams4));
}
@Test
@@ -1322,59 +1354,52 @@ public class HintManagerServiceTest {
halParams1.calculationType = GpuHeadroomParams.CalculationType.MIN;
GpuHeadroomParamsInternal params2 = new GpuHeadroomParamsInternal();
+ params2.calculationType = GpuHeadroomParams.CalculationType.AVERAGE;
+ params2.calculationWindowMillis = 123;
GpuHeadroomParams halParams2 = new GpuHeadroomParams();
- params2.calculationType =
- halParams2.calculationType = GpuHeadroomParams.CalculationType.AVERAGE;
+ halParams2.calculationType = GpuHeadroomParams.CalculationType.AVERAGE;
+ halParams2.calculationWindowMillis = 123;
float headroom1 = 0.1f;
- when(mIPowerMock.getGpuHeadroom(eq(halParams1))).thenReturn(headroom1);
+ GpuHeadroomResult halRet1 = GpuHeadroomResult.globalHeadroom(headroom1);
+ when(mIPowerMock.getGpuHeadroom(eq(halParams1))).thenReturn(halRet1);
float headroom2 = 0.2f;
- when(mIPowerMock.getGpuHeadroom(eq(halParams2))).thenReturn(headroom2);
+ GpuHeadroomResult halRet2 = GpuHeadroomResult.globalHeadroom(headroom2);
+ when(mIPowerMock.getGpuHeadroom(eq(halParams2))).thenReturn(halRet2);
HintManagerService service = createService();
clearInvocations(mIPowerMock);
service.getBinderServiceInstance().getGpuHeadroomMinIntervalMillis();
verify(mIPowerMock, times(0)).getGpuHeadroomMinIntervalMillis();
- assertEquals(headroom1, service.getBinderServiceInstance().getGpuHeadroom(params1),
- 0.01f);
- assertEquals(headroom2, service.getBinderServiceInstance().getGpuHeadroom(params2),
- 0.01f);
+ assertEquals(halRet1, service.getBinderServiceInstance().getGpuHeadroom(params1));
+ assertEquals(halRet2, service.getBinderServiceInstance().getGpuHeadroom(params2));
+ verify(mIPowerMock, times(2)).getGpuHeadroom(any());
verify(mIPowerMock, times(1)).getGpuHeadroom(eq(halParams1));
verify(mIPowerMock, times(1)).getGpuHeadroom(eq(halParams2));
// verify cache is working
clearInvocations(mIPowerMock);
- assertEquals(headroom1, service.getBinderServiceInstance().getGpuHeadroom(params1),
- 0.01f);
- assertEquals(headroom2, service.getBinderServiceInstance().getGpuHeadroom(params2),
- 0.01f);
- verify(mIPowerMock, times(0)).getGpuHeadroom(any());
+ assertEquals(halRet1, service.getBinderServiceInstance().getGpuHeadroom(params1));
+ assertEquals(halRet2, service.getBinderServiceInstance().getGpuHeadroom(params2));
+ verify(mIPowerMock, times(1)).getGpuHeadroom(any());
+ verify(mIPowerMock, times(0)).getGpuHeadroom(eq(halParams1));
+ verify(mIPowerMock, times(1)).getGpuHeadroom(eq(halParams2));
// after 1 more second it should be served with cache still
Thread.sleep(1000);
clearInvocations(mIPowerMock);
- assertEquals(headroom1, service.getBinderServiceInstance().getGpuHeadroom(params1),
- 0.01f);
- assertEquals(headroom2, service.getBinderServiceInstance().getGpuHeadroom(params2),
- 0.01f);
- verify(mIPowerMock, times(0)).getGpuHeadroom(any());
-
- // after 1.5 more second it should be served with cache still as timer reset
- Thread.sleep(1500);
- clearInvocations(mIPowerMock);
- assertEquals(headroom1, service.getBinderServiceInstance().getGpuHeadroom(params1),
- 0.01f);
- assertEquals(headroom2, service.getBinderServiceInstance().getGpuHeadroom(params2),
- 0.01f);
- verify(mIPowerMock, times(0)).getGpuHeadroom(any());
+ assertEquals(halRet1, service.getBinderServiceInstance().getGpuHeadroom(params1));
+ assertEquals(halRet2, service.getBinderServiceInstance().getGpuHeadroom(params2));
+ verify(mIPowerMock, times(1)).getGpuHeadroom(any());
+ verify(mIPowerMock, times(0)).getGpuHeadroom(eq(halParams1));
+ verify(mIPowerMock, times(1)).getGpuHeadroom(eq(halParams2));
// after 2+ seconds it should be served from HAL as it exceeds 2000 millis interval
- Thread.sleep(2100);
+ Thread.sleep(1100);
clearInvocations(mIPowerMock);
- assertEquals(headroom1, service.getBinderServiceInstance().getGpuHeadroom(params1),
- 0.01f);
- assertEquals(headroom2, service.getBinderServiceInstance().getGpuHeadroom(params2),
- 0.01f);
+ assertEquals(halRet1, service.getBinderServiceInstance().getGpuHeadroom(params1));
+ assertEquals(halRet2, service.getBinderServiceInstance().getGpuHeadroom(params2));
+ verify(mIPowerMock, times(2)).getGpuHeadroom(any());
verify(mIPowerMock, times(1)).getGpuHeadroom(eq(halParams1));
verify(mIPowerMock, times(1)).getGpuHeadroom(eq(halParams2));
}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/AmbientDisplayPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/AmbientDisplayPowerCalculatorTest.java
index c0be8652f303..4b91d8418fc3 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/AmbientDisplayPowerCalculatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/AmbientDisplayPowerCalculatorTest.java
@@ -79,8 +79,6 @@ public class AmbientDisplayPowerCalculatorTest {
// 100,000,00 uC / 1000 (micro-/milli-) / 360 (seconds/hour) = 27.777778 mAh
assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY))
.isWithin(PRECISION).of(27.777778);
- assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
}
@Test
@@ -140,8 +138,6 @@ public class AmbientDisplayPowerCalculatorTest {
// (seconds/hour) = 27.777778 mAh
assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY))
.isWithin(PRECISION).of(83.33333);
- assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
}
@Test
@@ -163,8 +159,6 @@ public class AmbientDisplayPowerCalculatorTest {
.isEqualTo(90 * MINUTE_IN_MS);
assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY))
.isWithin(PRECISION).of(15.0);
- assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
}
@Test
@@ -195,7 +189,5 @@ public class AmbientDisplayPowerCalculatorTest {
.isEqualTo(120 * MINUTE_IN_MS);
assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY))
.isWithin(PRECISION).of(35.0);
- assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
}
}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsAtomTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsAtomTest.java
index 5d50e6c9f715..9da89fcf2e84 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsAtomTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsAtomTest.java
@@ -311,10 +311,6 @@ public class BatteryUsageStatsAtomTest {
bus.getAggregateBatteryConsumer(AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE),
proto.deviceBatteryConsumer);
- for (int i = 0; i < BatteryConsumer.POWER_COMPONENT_COUNT; i++) {
- assertPowerComponentModel(i, abc.getPowerModel(i), proto);
- }
-
// Now for the UidBatteryConsumers.
final List<android.os.UidBatteryConsumer> uidConsumers = bus.getUidBatteryConsumers();
uidConsumers.sort((a, b) -> a.getUid() - b.getUid());
@@ -450,34 +446,6 @@ public class BatteryUsageStatsAtomTest {
}
}
- /**
- * Validates the PowerComponentModel object that matches powerComponent.
- */
- private void assertPowerComponentModel(int powerComponent,
- @BatteryConsumer.PowerModel int powerModel, BatteryUsageStatsAtomsProto proto) {
- boolean found = false;
- for (BatteryUsageStatsAtomsProto.PowerComponentModel powerComponentModel :
- proto.componentModels) {
- if (powerComponentModel.component == powerComponent) {
- if (found) {
- fail("Power component " + BatteryConsumer.powerComponentIdToString(
- powerComponent) + " found multiple times in the proto");
- }
- found = true;
- final int expectedPowerModel = BatteryConsumer.powerModelToProtoEnum(powerModel);
- assertEquals(expectedPowerModel, powerComponentModel.powerModel);
- }
- }
- if (!found) {
- final int model = BatteryConsumer.powerModelToProtoEnum(powerModel);
- assertEquals(
- "Power component " + BatteryConsumer.powerComponentIdToString(powerComponent)
- + " was not found in the proto but has a defined power model.",
- BatteryUsageStatsAtomsProto.PowerComponentModel.UNDEFINED,
- model);
- }
- }
-
/** Converts charge from milliamp hours (mAh) to decicoulombs (dC). */
private long convertMahToDc(double powerMah) {
return (long) (powerMah * 36 + 0.5);
@@ -486,7 +454,6 @@ public class BatteryUsageStatsAtomTest {
private BatteryUsageStats buildBatteryUsageStats() {
final BatteryUsageStats.Builder builder =
new BatteryUsageStats.Builder(new String[]{"CustomConsumer1", "CustomConsumer2"},
- /* includePowerModels */ true,
/* includeProcessStats */ true,
/* includeScreenStateData */ false,
/* includePowerStateData */ false,
@@ -524,13 +491,13 @@ public class BatteryUsageStatsAtomTest {
final BatteryConsumer.Key keyCached = uidBuilder.getKey(BatteryConsumer.POWER_COMPONENT_CPU,
BatteryConsumer.PROCESS_STATE_CACHED);
- uidBuilder.addConsumedPower(keyFg, 9100, BatteryConsumer.POWER_MODEL_POWER_PROFILE)
+ uidBuilder.addConsumedPower(keyFg, 9100)
.addUsageDurationMillis(keyFg, 8100)
- .addConsumedPower(keyBg, 9200, BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION)
+ .addConsumedPower(keyBg, (double) 9200)
.addUsageDurationMillis(keyBg, 8200)
- .addConsumedPower(keyFgs, 9300, BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION)
+ .addConsumedPower(keyFgs, (double) 9300)
.addUsageDurationMillis(keyFgs, 8300)
- .addConsumedPower(keyCached, 9400, BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION)
+ .addConsumedPower(keyCached, (double) 9400)
.addUsageDurationMillis(keyCached, 8400);
final BatteryConsumer.Key keyCustomFg = uidBuilder.getKey(
@@ -539,10 +506,8 @@ public class BatteryUsageStatsAtomTest {
final BatteryConsumer.Key keyCustomBg = uidBuilder.getKey(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID,
BatteryConsumer.PROCESS_STATE_BACKGROUND);
- uidBuilder.addConsumedPower(
- keyCustomFg, 100, BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
- uidBuilder.addConsumedPower(
- keyCustomBg, 350, BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
+ uidBuilder.addConsumedPower(keyCustomFg, 100);
+ uidBuilder.addConsumedPower(keyCustomBg, 350);
builder.getOrCreateUidBatteryConsumerBuilder(UID_1)
.setPackageWithHighestDrain("myPackage1")
@@ -557,14 +522,11 @@ public class BatteryUsageStatsAtomTest {
builder.getAggregateBatteryConsumerBuilder(AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
.addConsumedPower(30000)
.addConsumedPower(
- BatteryConsumer.POWER_COMPONENT_CPU, 20100,
- BatteryConsumer.POWER_MODEL_POWER_PROFILE)
+ BatteryConsumer.POWER_COMPONENT_CPU, 20100)
.addConsumedPower(
- BatteryConsumer.POWER_COMPONENT_AUDIO, 0,
- BatteryConsumer.POWER_MODEL_POWER_PROFILE) // Empty
+ BatteryConsumer.POWER_COMPONENT_AUDIO, 0) // Empty
.addConsumedPower(
- BatteryConsumer.POWER_COMPONENT_CAMERA, 20150,
- BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION)
+ BatteryConsumer.POWER_COMPONENT_CAMERA, 20150)
.addConsumedPower(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 20200)
.addUsageDurationMillis(
@@ -576,8 +538,7 @@ public class BatteryUsageStatsAtomTest {
builder.getAggregateBatteryConsumerBuilder(
BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
.addConsumedPower(
- BatteryConsumer.POWER_COMPONENT_CPU, 10100,
- BatteryConsumer.POWER_MODEL_POWER_PROFILE)
+ BatteryConsumer.POWER_COMPONENT_CPU, 10100)
.addConsumedPower(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 10200);
@@ -587,7 +548,7 @@ public class BatteryUsageStatsAtomTest {
@Test
public void testLargeAtomTruncated() throws Exception {
final BatteryUsageStats.Builder builder =
- new BatteryUsageStats.Builder(new String[0], true, false, false, false, 0);
+ new BatteryUsageStats.Builder(new String[0], false, false, false, 0);
// If not truncated, this BatteryUsageStats object would generate a proto buffer
// significantly larger than 50 Kb
for (int i = 0; i < 3000; i++) {
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 383616eb59eb..a3c7ece386c7 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
@@ -423,8 +423,6 @@ public class BatteryUsageStatsRule implements TestRule {
mBatteryUsageStats = null;
}
final String[] customPowerComponentNames = mBatteryStats.getCustomEnergyConsumerNames();
- final boolean includePowerModels = (query.getFlags()
- & BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_MODELS) != 0;
final boolean includeProcessStateData = (query.getFlags()
& BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_PROCESS_STATE_DATA) != 0;
final boolean includeScreenStateData = (query.getFlags()
@@ -433,7 +431,7 @@ public class BatteryUsageStatsRule implements TestRule {
& BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_STATE) != 0;
final double minConsumedPowerThreshold = query.getMinConsumedPowerThreshold();
BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(
- customPowerComponentNames, includePowerModels, includeProcessStateData,
+ customPowerComponentNames, includeProcessStateData,
includeScreenStateData, includePowerStateData, minConsumedPowerThreshold);
SparseArray<? extends BatteryStats.Uid> uidStats = mBatteryStats.getUidStats();
for (int i = 0; i < uidStats.size(); i++) {
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java
index 9771da590e37..dd50431b598e 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java
@@ -17,8 +17,6 @@
package com.android.server.power.stats;
import static android.os.BatteryConsumer.POWER_COMPONENT_ANY;
-import static android.os.BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION;
-import static android.os.BatteryConsumer.POWER_MODEL_UNDEFINED;
import static android.os.BatteryConsumer.PROCESS_STATE_BACKGROUND;
import static android.os.BatteryConsumer.PROCESS_STATE_CACHED;
import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND;
@@ -237,7 +235,7 @@ public class BatteryUsageStatsTest {
final BatteryUsageStats stats1 = buildBatteryUsageStats1(false).build();
final BatteryUsageStats stats2 = buildBatteryUsageStats2(new String[]{"FOO"}, true).build();
final BatteryUsageStats sum =
- new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true, true, true, 0)
+ new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true, true, 0)
.add(stats1)
.add(stats2)
.build();
@@ -248,15 +246,13 @@ public class BatteryUsageStatsTest {
for (UidBatteryConsumer uidBatteryConsumer : uidBatteryConsumers) {
if (uidBatteryConsumer.getUid() == APP_UID1) {
assertUidBatteryConsumer(uidBatteryConsumer, 1200 + 924, null,
- 5321, 6900, 532, 423, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 400 + 345,
- POWER_MODEL_UNDEFINED,
+ 5321, 6900, 532, 423, 400 + 345,
500 + 456, 1167, 1478,
true, 3554, 4732, 3998, 444, 3554, 15542, 3776, 17762, 3998, 19982,
444, 1110);
} else if (uidBatteryConsumer.getUid() == APP_UID2) {
assertUidBatteryConsumer(uidBatteryConsumer, 1332, "bar",
- 1111, 2220, 2, 333, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 444,
- BatteryConsumer.POWER_MODEL_POWER_PROFILE,
+ 1111, 2220, 2, 333, 444,
555, 666, 777,
true, 1777, 2443, 1999, 321, 1777, 7771, 1888, 8881, 1999, 9991,
321, 654);
@@ -280,7 +276,7 @@ public class BatteryUsageStatsTest {
@Test
public void testAdd_customComponentMismatch() throws Exception {
final BatteryUsageStats.Builder builder =
- new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true, true, true, 0);
+ new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true, true, 0);
final BatteryUsageStats stats = buildBatteryUsageStats2(new String[]{"BAR"}, false).build();
assertThrows(IllegalArgumentException.class, () -> builder.add(stats));
@@ -291,7 +287,7 @@ public class BatteryUsageStatsTest {
@Test
public void testAdd_processStateDataMismatch() throws Exception {
final BatteryUsageStats.Builder builder =
- new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true, true, true, 0);
+ new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true, true, 0);
final BatteryUsageStats stats = buildBatteryUsageStats2(new String[]{"FOO"}, false).build();
assertThrows(IllegalArgumentException.class, () -> builder.add(stats));
@@ -328,7 +324,7 @@ public class BatteryUsageStatsTest {
final MockBatteryStatsImpl batteryStats = new MockBatteryStatsImpl(clocks);
final BatteryUsageStats.Builder builder =
- new BatteryUsageStats.Builder(new String[]{"FOO"}, true, true,
+ new BatteryUsageStats.Builder(new String[]{"FOO"}, true,
includeScreenState, includePowerState, 0)
.setBatteryCapacity(4000)
.setDischargePercentage(20)
@@ -339,8 +335,8 @@ public class BatteryUsageStatsTest {
addUidBatteryConsumer(builder, batteryStats, APP_UID1, "foo",
1000, 1500, 500,
- 300, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 400,
- BatteryConsumer.POWER_MODEL_POWER_PROFILE, 500, 600, 800,
+ 300, 400,
+ 500, 600, 800,
1777, 7771, 1888, 8881, 1999, 9991, 123, 456);
addAggregateBatteryConsumer(builder,
@@ -373,7 +369,7 @@ public class BatteryUsageStatsTest {
final MockBatteryStatsImpl batteryStats = new MockBatteryStatsImpl(clocks);
final BatteryUsageStats.Builder builder =
- new BatteryUsageStats.Builder(customPowerComponentNames, true,
+ new BatteryUsageStats.Builder(customPowerComponentNames,
includeProcessStateData, true, true, 0);
builder.setDischargePercentage(30)
.setDischargedPowerRange(1234, 2345)
@@ -382,14 +378,14 @@ public class BatteryUsageStatsTest {
addUidBatteryConsumer(builder, batteryStats, APP_UID1, null,
4321, 5400, 32,
- 123, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 345, POWER_MODEL_ENERGY_CONSUMPTION,
+ 123, 345,
456, 567, 678,
1777, 7771, 1888, 8881, 1999, 9991, 321, 654);
addUidBatteryConsumer(builder, batteryStats, APP_UID2, "bar",
1111, 2220, 2,
- 333, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 444,
- BatteryConsumer.POWER_MODEL_POWER_PROFILE, 555, 666, 777,
+ 333, 444,
+ 555, 666, 777,
1777, 7771, 1888, 8881, 1999, 9991, 321, 654);
addAggregateBatteryConsumer(builder,
@@ -409,7 +405,7 @@ public class BatteryUsageStatsTest {
MockBatteryStatsImpl batteryStats, int uid, String packageWithHighestDrain,
int timeInProcessStateForeground, int timeInProcessStateBackground,
int timeInProcessStateForegroundService, double screenPower,
- int screenPowerModel, double cpuPower, int cpuPowerModel, double customComponentPower,
+ double cpuPower, double customComponentPower,
int cpuDuration, int customComponentDuration, double cpuPowerForeground,
int cpuDurationForeground, double cpuPowerBackground, int cpuDurationBackground,
double cpuPowerFgs, int cpuDurationFgs, double cpuPowerCached, long cpuDurationCached) {
@@ -423,9 +419,9 @@ public class BatteryUsageStatsTest {
.setTimeInProcessStateMs(PROCESS_STATE_FOREGROUND_SERVICE,
timeInProcessStateForegroundService)
.addConsumedPower(
- BatteryConsumer.POWER_COMPONENT_SCREEN, screenPower, screenPowerModel)
+ BatteryConsumer.POWER_COMPONENT_SCREEN, screenPower)
.addConsumedPower(
- BatteryConsumer.POWER_COMPONENT_CPU, cpuPower, cpuPowerModel)
+ BatteryConsumer.POWER_COMPONENT_CPU, cpuPower)
.addConsumedPower(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, customComponentPower)
.addUsageDurationMillis(
@@ -460,21 +456,15 @@ public class BatteryUsageStatsTest {
: uidBuilder.getKey(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID,
BatteryConsumer.PROCESS_STATE_BACKGROUND);
- uidBuilder
- .addConsumedPower(cpuFgKey, cpuPowerForeground,
- BatteryConsumer.POWER_MODEL_POWER_PROFILE)
+ uidBuilder.addConsumedPower(cpuFgKey, cpuPowerForeground)
.addUsageDurationMillis(cpuFgKey, cpuDurationForeground)
- .addConsumedPower(cpuBgKey, cpuPowerBackground,
- BatteryConsumer.POWER_MODEL_POWER_PROFILE)
+ .addConsumedPower(cpuBgKey, cpuPowerBackground)
.addUsageDurationMillis(cpuBgKey, cpuDurationBackground)
- .addConsumedPower(cpuFgsKey, cpuPowerFgs,
- BatteryConsumer.POWER_MODEL_POWER_PROFILE)
+ .addConsumedPower(cpuFgsKey, cpuPowerFgs)
.addUsageDurationMillis(cpuFgsKey, cpuDurationFgs)
- .addConsumedPower(cachedKey, cpuPowerCached,
- BatteryConsumer.POWER_MODEL_POWER_PROFILE)
+ .addConsumedPower(cachedKey, cpuPowerCached)
.addUsageDurationMillis(cachedKey, cpuDurationCached)
- .addConsumedPower(customBgKey, customComponentPower,
- BatteryConsumer.POWER_MODEL_UNDEFINED)
+ .addConsumedPower(customBgKey, customComponentPower)
.addUsageDurationMillis(customBgKey, customComponentDuration);
}
}
@@ -518,18 +508,13 @@ public class BatteryUsageStatsTest {
BatteryConsumer.PROCESS_STATE_UNSPECIFIED,
BatteryConsumer.SCREEN_STATE_OTHER,
BatteryConsumer.POWER_STATE_OTHER);
- aggBuilder
- .addConsumedPower(cpuBatScrOn, cpuPowerBatScrOn,
- BatteryConsumer.POWER_MODEL_POWER_PROFILE)
+ aggBuilder.addConsumedPower(cpuBatScrOn, cpuPowerBatScrOn)
.addUsageDurationMillis(cpuBatScrOn, cpuDurationBatScrOn)
- .addConsumedPower(cpuBatScrOff, cpuPowerBatScrOff,
- BatteryConsumer.POWER_MODEL_POWER_PROFILE)
+ .addConsumedPower(cpuBatScrOff, cpuPowerBatScrOff)
.addUsageDurationMillis(cpuBatScrOff, cpuDurationBatScrOff)
- .addConsumedPower(cpuChgScrOn, cpuPowerChgScrOn,
- BatteryConsumer.POWER_MODEL_POWER_PROFILE)
+ .addConsumedPower(cpuChgScrOn, cpuPowerChgScrOn)
.addUsageDurationMillis(cpuChgScrOn, cpuDurationChgScrOn)
- .addConsumedPower(cpuChgScrOff, cpuPowerChgScrOff,
- BatteryConsumer.POWER_MODEL_POWER_PROFILE)
+ .addConsumedPower(cpuChgScrOff, cpuPowerChgScrOff)
.addUsageDurationMillis(cpuChgScrOff, cpuDurationChgScrOff);
}
}
@@ -544,8 +529,7 @@ public class BatteryUsageStatsTest {
for (UidBatteryConsumer uidBatteryConsumer : uidBatteryConsumers) {
if (uidBatteryConsumer.getUid() == APP_UID1) {
assertUidBatteryConsumer(uidBatteryConsumer, 1200, "foo",
- 1000, 1500, 500, 300, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 400,
- BatteryConsumer.POWER_MODEL_POWER_PROFILE,
+ 1000, 1500, 500, 300, 400,
500, 600, 800,
true, 1777, 2388, 1999, 123, 1777, 7771, 1888, 8881, 1999, 9991, 123, 456);
} else {
@@ -596,8 +580,8 @@ public class BatteryUsageStatsTest {
private void assertUidBatteryConsumer(UidBatteryConsumer uidBatteryConsumer,
double consumedPower, String packageWithHighestDrain, int timeInProcessStateForeground,
int timeInProcessStateBackground, int timeInProcessStateForegroundService,
- int screenPower, int screenPowerModel, double cpuPower,
- int cpuPowerModel, double customComponentPower, int cpuDuration,
+ int screenPower, double cpuPower,
+ double customComponentPower, int cpuDuration,
int customComponentDuration, boolean processStateDataIncluded,
double totalPowerForeground, double totalPowerBackground, double totalPowerFgs,
double totalPowerCached, double cpuPowerForeground, int cpuDurationForeground,
@@ -620,12 +604,8 @@ public class BatteryUsageStatsTest {
PROCESS_STATE_FOREGROUND_SERVICE)).isEqualTo(timeInProcessStateForegroundService);
assertThat(uidBatteryConsumer.getConsumedPower(
BatteryConsumer.POWER_COMPONENT_SCREEN)).isEqualTo(screenPower);
- assertThat(uidBatteryConsumer.getPowerModel(
- BatteryConsumer.POWER_COMPONENT_SCREEN)).isEqualTo(screenPowerModel);
assertThat(uidBatteryConsumer.getConsumedPower(
BatteryConsumer.POWER_COMPONENT_CPU)).isEqualTo(cpuPower);
- assertThat(uidBatteryConsumer.getPowerModel(
- BatteryConsumer.POWER_COMPONENT_CPU)).isEqualTo(cpuPowerModel);
assertThat(uidBatteryConsumer.getConsumedPower(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo(customComponentPower);
assertThat(uidBatteryConsumer.getUsageDurationMillis(
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BluetoothPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BluetoothPowerCalculatorTest.java
index fe6424f91d83..c9cb0dfa98e4 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BluetoothPowerCalculatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BluetoothPowerCalculatorTest.java
@@ -82,16 +82,16 @@ public class BluetoothPowerCalculatorTest {
assertBluetoothPowerAndDuration(
mStatsRule.getUidBatteryConsumer(Process.BLUETOOTH_UID),
- 0.06944, 3000, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+ 0.06944, 3000);
assertBluetoothPowerAndDuration(
mStatsRule.getUidBatteryConsumer(APP_UID),
- 0.19444, 9000, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+ 0.19444, 9000);
assertBluetoothPowerAndDuration(
mStatsRule.getDeviceBatteryConsumer(),
- 0.26388, 12000, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+ 0.26388, 12000);
assertBluetoothPowerAndDuration(
mStatsRule.getAppsBatteryConsumer(),
- 0.26388, 12000, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+ 0.26388, 12000);
}
@Test
@@ -144,8 +144,6 @@ public class BluetoothPowerCalculatorTest {
.isEqualTo(6166);
assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_BLUETOOTH))
.isWithin(PRECISION).of(0.1226666);
- assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_BLUETOOTH))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
final BatteryConsumer.Key foreground = uidConsumer.getKey(
BatteryConsumer.POWER_COMPONENT_BLUETOOTH,
@@ -178,16 +176,16 @@ public class BluetoothPowerCalculatorTest {
assertBluetoothPowerAndDuration(
mStatsRule.getUidBatteryConsumer(Process.BLUETOOTH_UID),
- 0.08216, 3583, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+ 0.08216, 3583);
assertBluetoothPowerAndDuration(
mStatsRule.getUidBatteryConsumer(APP_UID),
- 0.18169, 8416, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+ 0.18169, 8416);
assertBluetoothPowerAndDuration(
mStatsRule.getDeviceBatteryConsumer(),
- 0.30030, 12000, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+ 0.30030, 12000);
assertBluetoothPowerAndDuration(
mStatsRule.getAppsBatteryConsumer(),
- 0.26386, 11999, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+ 0.26386, 11999);
}
@Test
@@ -202,16 +200,16 @@ public class BluetoothPowerCalculatorTest {
assertBluetoothPowerAndDuration(
mStatsRule.getUidBatteryConsumer(Process.BLUETOOTH_UID),
- 0.10378, 3583, BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
+ 0.10378, 3583);
assertBluetoothPowerAndDuration(
mStatsRule.getUidBatteryConsumer(APP_UID),
- 0.22950, 8416, BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
+ 0.22950, 8416);
assertBluetoothPowerAndDuration(
mStatsRule.getDeviceBatteryConsumer(),
- 0.33333, 12000, BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
+ 0.33333, 12000);
assertBluetoothPowerAndDuration(
mStatsRule.getAppsBatteryConsumer(),
- 0.33329, 11999, BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
+ 0.33329, 11999);
}
@Test
@@ -264,8 +262,6 @@ public class BluetoothPowerCalculatorTest {
.isEqualTo(6166);
assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_BLUETOOTH))
.isWithin(PRECISION).of(0.8220561);
- assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_BLUETOOTH))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
final BatteryConsumer.Key foreground = uidConsumer.getKey(
BatteryConsumer.POWER_COMPONENT_BLUETOOTH,
@@ -299,16 +295,16 @@ public class BluetoothPowerCalculatorTest {
assertBluetoothPowerAndDuration(
mStatsRule.getUidBatteryConsumer(Process.BLUETOOTH_UID),
- 0.08216, 3583, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+ 0.08216, 3583);
assertBluetoothPowerAndDuration(
mStatsRule.getUidBatteryConsumer(APP_UID),
- 0.18169, 8416, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+ 0.18169, 8416);
assertBluetoothPowerAndDuration(
mStatsRule.getDeviceBatteryConsumer(),
- 0.26388, 12000, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+ 0.26388, 12000);
assertBluetoothPowerAndDuration(
mStatsRule.getAppsBatteryConsumer(),
- 0.26386, 11999, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+ 0.26386, 11999);
}
private void setupBluetoothEnergyInfo(long reportedEnergyUc, long consumedEnergyUc) {
@@ -326,14 +322,12 @@ public class BluetoothPowerCalculatorTest {
}
private void assertBluetoothPowerAndDuration(@Nullable BatteryConsumer batteryConsumer,
- double powerMah, int durationMs, @BatteryConsumer.PowerModel int powerModel) {
+ double powerMah, int durationMs) {
assertThat(batteryConsumer).isNotNull();
double consumedPower = batteryConsumer.getConsumedPower(
BatteryConsumer.POWER_COMPONENT_BLUETOOTH);
assertThat(consumedPower).isWithin(PRECISION).of(powerMah);
- assertThat(batteryConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_BLUETOOTH))
- .isEqualTo(powerModel);
long usageDurationMillis = batteryConsumer.getUsageDurationMillis(
BatteryConsumer.POWER_COMPONENT_BLUETOOTH);
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CameraPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CameraPowerCalculatorTest.java
index 7225f2d3995c..4cd3857a80bb 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/CameraPowerCalculatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CameraPowerCalculatorTest.java
@@ -70,16 +70,12 @@ public class CameraPowerCalculatorTest {
.isEqualTo(1000);
assertThat(app1Consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA))
.isWithin(PRECISION).of(0.1);
- assertThat(app1Consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CAMERA))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
UidBatteryConsumer app2Consumer = mStatsRule.getUidBatteryConsumer(APP2_UID);
assertThat(app2Consumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_CAMERA))
.isEqualTo(2000);
assertThat(app2Consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA))
.isWithin(PRECISION).of(0.2);
- assertThat(app2Consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CAMERA))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
final BatteryConsumer deviceBatteryConsumer = mStatsRule.getDeviceBatteryConsumer();
assertThat(deviceBatteryConsumer
@@ -87,8 +83,6 @@ public class CameraPowerCalculatorTest {
.isEqualTo(3000);
assertThat(deviceBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA))
.isWithin(PRECISION).of(0.3);
- assertThat(deviceBatteryConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CAMERA))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
final BatteryConsumer appsBatteryConsumer = mStatsRule.getAppsBatteryConsumer();
assertThat(appsBatteryConsumer
@@ -96,8 +90,6 @@ public class CameraPowerCalculatorTest {
.isEqualTo(3000);
assertThat(appsBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA))
.isWithin(PRECISION).of(0.3);
- assertThat(appsBatteryConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CAMERA))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
}
@Test
@@ -122,16 +114,12 @@ public class CameraPowerCalculatorTest {
.isEqualTo(1000);
assertThat(app1Consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA))
.isWithin(PRECISION).of(0.2);
- assertThat(app1Consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CAMERA))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
UidBatteryConsumer app2Consumer = mStatsRule.getUidBatteryConsumer(APP2_UID);
assertThat(app2Consumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_CAMERA))
.isEqualTo(2000);
assertThat(app2Consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA))
.isWithin(PRECISION).of(0.3);
- assertThat(app2Consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CAMERA))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
final BatteryConsumer deviceBatteryConsumer = mStatsRule.getDeviceBatteryConsumer();
assertThat(deviceBatteryConsumer
@@ -139,8 +127,6 @@ public class CameraPowerCalculatorTest {
.isEqualTo(3000);
assertThat(deviceBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA))
.isWithin(PRECISION).of(0.5);
- assertThat(deviceBatteryConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CAMERA))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
final BatteryConsumer appsBatteryConsumer = mStatsRule.getAppsBatteryConsumer();
assertThat(appsBatteryConsumer
@@ -148,7 +134,5 @@ public class CameraPowerCalculatorTest {
.isEqualTo(3000);
assertThat(appsBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA))
.isWithin(PRECISION).of(0.5);
- assertThat(appsBatteryConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CAMERA))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
}
}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerCalculatorTest.java
index 4cea72835fba..527db67a84ae 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerCalculatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerCalculatorTest.java
@@ -192,8 +192,6 @@ public class CpuPowerCalculatorTest {
.isEqualTo(3333);
assertThat(uidConsumer1.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU))
.isWithin(PRECISION).of(1.031677);
- assertThat(uidConsumer1.getPowerModel(BatteryConsumer.POWER_COMPONENT_CPU))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
assertThat(uidConsumer1.getPackageWithHighestDrain()).isEqualTo("bar");
UidBatteryConsumer uidConsumer2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
@@ -201,21 +199,15 @@ public class CpuPowerCalculatorTest {
.isEqualTo(7777);
assertThat(uidConsumer2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU))
.isWithin(PRECISION).of(2.489544);
- assertThat(uidConsumer2.getPowerModel(BatteryConsumer.POWER_COMPONENT_CPU))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
assertThat(uidConsumer2.getPackageWithHighestDrain()).isNull();
final BatteryConsumer deviceBatteryConsumer = mStatsRule.getDeviceBatteryConsumer();
assertThat(deviceBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU))
.isWithin(PRECISION).of(3.52122);
- assertThat(deviceBatteryConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CPU))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
final BatteryConsumer appsBatteryConsumer = mStatsRule.getAppsBatteryConsumer();
assertThat(appsBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU))
.isWithin(PRECISION).of(3.52122);
- assertThat(appsBatteryConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CPU))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
}
@Test
@@ -264,8 +256,6 @@ public class CpuPowerCalculatorTest {
.isEqualTo(3333);
assertThat(uidConsumer1.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU))
.isWithin(PRECISION).of(3.18877);
- assertThat(uidConsumer1.getPowerModel(BatteryConsumer.POWER_COMPONENT_CPU))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
assertThat(uidConsumer1.getPackageWithHighestDrain()).isEqualTo("bar");
UidBatteryConsumer uidConsumer2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
@@ -273,21 +263,15 @@ public class CpuPowerCalculatorTest {
.isEqualTo(7777);
assertThat(uidConsumer2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU))
.isWithin(PRECISION).of(7.44072);
- assertThat(uidConsumer2.getPowerModel(BatteryConsumer.POWER_COMPONENT_CPU))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
assertThat(uidConsumer2.getPackageWithHighestDrain()).isNull();
final BatteryConsumer deviceBatteryConsumer = mStatsRule.getDeviceBatteryConsumer();
assertThat(deviceBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU))
.isWithin(PRECISION).of(10.62949);
- assertThat(deviceBatteryConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CPU))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
final BatteryConsumer appsBatteryConsumer = mStatsRule.getDeviceBatteryConsumer();
assertThat(appsBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU))
.isWithin(PRECISION).of(10.62949);
- assertThat(appsBatteryConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CPU))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
}
@Test
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/GnssPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/GnssPowerCalculatorTest.java
index 3b5658c2e40a..506bab4b3600 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/GnssPowerCalculatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/GnssPowerCalculatorTest.java
@@ -68,20 +68,14 @@ public class GnssPowerCalculatorTest {
.isEqualTo(1000);
assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_GNSS))
.isWithin(PRECISION).of(0.1);
- assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_GNSS))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_GNSS))
.isWithin(PRECISION).of(0.1);
- assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_GNSS))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_GNSS))
.isWithin(PRECISION).of(0.1);
- assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_GNSS))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
}
@Test
@@ -107,27 +101,19 @@ public class GnssPowerCalculatorTest {
.isEqualTo(1000);
assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_GNSS))
.isWithin(PRECISION).of(2.77777);
- assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_GNSS))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
UidBatteryConsumer consumer2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
assertThat(consumer2.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_GNSS))
.isEqualTo(2000);
assertThat(consumer2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_GNSS))
.isWithin(PRECISION).of(5.55555);
- assertThat(consumer2.getPowerModel(BatteryConsumer.POWER_COMPONENT_GNSS))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_GNSS))
.isWithin(PRECISION).of(8.333333);
- assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_GNSS))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_GNSS))
.isWithin(PRECISION).of(8.333333);
- assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_GNSS))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
}
}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java
index 9b810bc01b4d..eba820e95783 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java
@@ -164,8 +164,6 @@ public class MobileRadioPowerCalculatorTest {
// = 4604000 mA-ms or 1.27888 mA-h
assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
.isWithin(PRECISION).of(1.27888);
- assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
// 720 mA * 100 ms (level 0 TX drain rate * level 0 TX duration)
// + 1080 mA * 200 ms (level 1 TX drain rate * level 1 TX duration)
@@ -178,22 +176,16 @@ public class MobileRadioPowerCalculatorTest {
BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
.isWithin(PRECISION).of(0.94);
- assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
// 3/4 of total packets were sent by APP_UID so 75% of total
UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
.isWithin(PRECISION).of(0.705);
- assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
// Rest should go to the other app
UidBatteryConsumer uidConsumer2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
assertThat(uidConsumer2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
.isWithin(PRECISION).of(0.235);
- assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
}
@Test
@@ -321,8 +313,6 @@ public class MobileRadioPowerCalculatorTest {
// = 5177753 mA-ms or 1.43826 mA-h total consumption
assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
.isWithin(PRECISION).of(1.43826);
- assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
// 720 mA * 100 ms (level 0 TX drain rate * level 0 TX duration)
// + 1080 mA * 200 ms (level 1 TX drain rate * level 1 TX duration)
@@ -335,22 +325,16 @@ public class MobileRadioPowerCalculatorTest {
BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
.isWithin(PRECISION).of(1.09938);
- assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
// 3/4 of total packets were sent by APP_UID so 75% of total
UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
.isWithin(PRECISION).of(0.82453);
- assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
// Rest should go to the other app
UidBatteryConsumer uidConsumer2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
assertThat(uidConsumer2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
.isWithin(PRECISION).of(0.27484);
- assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
}
@Test
@@ -441,8 +425,6 @@ public class MobileRadioPowerCalculatorTest {
// = 4604000 mA-ms or 1.27888 mA-h
assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
.isWithin(PRECISION).of(1.27888);
- assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
// 720 mA * 100 ms (level 0 TX drain rate * level 0 TX duration)
// + 1080 mA * 200 ms (level 1 TX drain rate * level 1 TX duration)
@@ -455,22 +437,16 @@ public class MobileRadioPowerCalculatorTest {
BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
.isWithin(PRECISION).of(0.94);
- assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
// 3/4 of total packets were sent by APP_UID so 75% of total
UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
.isWithin(PRECISION).of(0.705);
- assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
// Rest should go to the other app
UidBatteryConsumer uidConsumer2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
assertThat(uidConsumer2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
.isWithin(PRECISION).of(0.235);
- assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
}
@Test
@@ -648,24 +624,16 @@ public class MobileRadioPowerCalculatorTest {
// 200ms phone on duration / 2000 total duration * 2.77778 mAh = 0.27777
assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
.isWithin(PRECISION).of(2.5);
- assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_PHONE))
.isWithin(PRECISION).of(0.27778);
- assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_PHONE))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
.isWithin(PRECISION).of(1.38541);
- assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
.isWithin(PRECISION).of(1.38541);
- assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
}
@Test
@@ -782,12 +750,8 @@ public class MobileRadioPowerCalculatorTest {
// 1000ms phone on duration / 10000 total duration * 2.77778 mAh = 0.27777
assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
.isWithin(PRECISION).of(2.5);
- assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_PHONE))
.isWithin(PRECISION).of(0.27778);
- assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_PHONE))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
// CDMA2000 [Tx0, Tx1, Tx2, Tx3, Tx4, Rx] drain * duration
// [720, 1080, 1440, 1800, 2160, 1440] mA . [10, 11, 12, 13, 14, 15] ms = 111600 mA-ms
@@ -817,8 +781,6 @@ public class MobileRadioPowerCalculatorTest {
BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
.isWithin(PRECISION).of(1.91094);
- assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
// 240 ms Rx Time, 1110 ms Tx Time, 1350 ms active time
// 150 App 1 Rx Packets, 10 App 1 Tx packets
@@ -841,15 +803,11 @@ public class MobileRadioPowerCalculatorTest {
UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
.isWithin(PRECISION).of(1.27574);
- assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
// Rest should go to the other app
UidBatteryConsumer uidConsumer2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
assertThat(uidConsumer2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
.isWithin(PRECISION).of(0.63520);
- assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
}
@Test
@@ -936,12 +894,8 @@ public class MobileRadioPowerCalculatorTest {
// 1000ms phone on duration / 10000 total duration * 2.77778 mAh = 0.27777
assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
.isWithin(PRECISION).of(2.5);
- assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_PHONE))
.isWithin(PRECISION).of(0.27778);
- assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_PHONE))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
// Estimated Rx/Tx modem consumption = 0.94 mAh
@@ -949,14 +903,10 @@ public class MobileRadioPowerCalculatorTest {
// 2.5 * 0.94 / 1.27888 = 1.83754 mAh
assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
.isWithin(PRECISION).of(1.83754);
- assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
.isWithin(PRECISION).of(1.83754);
- assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
}
@Test
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/ScreenPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/ScreenPowerCalculatorTest.java
index 2da98e8b9a61..7f20035253f0 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/ScreenPowerCalculatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/ScreenPowerCalculatorTest.java
@@ -104,8 +104,6 @@ public class ScreenPowerCalculatorTest {
// Uid1 charge = 200000000 + 5 / 45 * 300000000 mAs = 64.81 mAh
assertThat(uid1.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
.isWithin(PRECISION).of(64.81481);
- assertThat(uid1.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
UidBatteryConsumer uid2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
assertThat(uid2.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
@@ -116,8 +114,6 @@ public class ScreenPowerCalculatorTest {
// Uid2 charge = 40 / 45 * 300000000 + 100000000 mAs = 101.85 mAh
assertThat(uid2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
.isWithin(PRECISION).of(101.85185);
- assertThat(uid2.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
assertThat(deviceConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
@@ -126,8 +122,6 @@ public class ScreenPowerCalculatorTest {
// 600000000 uAs * (1 mA / 1000 uA) * (1 h / 3600 s) = 166.66666 mAh
assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
.isWithin(PRECISION).of(166.66666);
- assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
assertThat(appsConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
@@ -135,11 +129,8 @@ public class ScreenPowerCalculatorTest {
assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
.isWithin(PRECISION).of(166.66666);
- assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
}
-
@Test
public void testMeasuredEnergyBasedModel_multiDisplay() {
mStatsRule.initMeasuredEnergyStatsLocked()
@@ -202,8 +193,6 @@ public class ScreenPowerCalculatorTest {
// (600000000 + 800000000) uAs * (1 mA / 1000 uA) * (1 h / 3600 s) = 166.66666 mAh
assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
.isWithin(PRECISION).of(388.88888);
- assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
UidBatteryConsumer uid1 = mStatsRule.getUidBatteryConsumer(APP_UID1);
assertThat(uid1.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
@@ -214,8 +203,6 @@ public class ScreenPowerCalculatorTest {
// Uid1 charge = 20 / 80 * 600000000 mAs = 41.66666 mAh
assertThat(uid1.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
.isWithin(PRECISION).of(41.66666);
- assertThat(uid1.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
UidBatteryConsumer uid2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
assertThat(uid2.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
@@ -226,17 +213,12 @@ public class ScreenPowerCalculatorTest {
// Uid1 charge = 60 / 80 * 600000000 + 800000000 mAs = 347.22222 mAh
assertThat(uid2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
.isWithin(PRECISION).of(347.22222);
- assertThat(uid2.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
assertThat(appsConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
.isEqualTo(110 * MINUTE_IN_MS);
assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
.isWithin(PRECISION).of(388.88888);
- assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
-
}
@Test
@@ -277,8 +259,6 @@ public class ScreenPowerCalculatorTest {
// Uid1 charge = 20 / 80 * 92.0 = 23.0 mAh
assertThat(uid1.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
.isWithin(PRECISION).of(23.0);
- assertThat(uid1.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
UidBatteryConsumer uid2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
assertThat(uid2.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
@@ -288,27 +268,20 @@ public class ScreenPowerCalculatorTest {
// Uid2 charge = 60 / 80 * 92.0 = 69.0 mAh
assertThat(uid2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
.isWithin(PRECISION).of(69.0);
- assertThat(uid2.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
assertThat(deviceConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
.isEqualTo(80 * MINUTE_IN_MS);
assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
.isWithin(PRECISION).of(92);
- assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
assertThat(appsConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
.isEqualTo(80 * MINUTE_IN_MS);
assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
.isWithin(PRECISION).of(92);
- assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
}
-
@Test
public void testPowerProfileBasedModel_multiDisplay() {
mStatsRule.setAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_ON, 1, 60.0)
@@ -364,8 +337,6 @@ public class ScreenPowerCalculatorTest {
// 92 + 60 * 0.5 + 10 * 0.1 + 90 * 0.2 + 30 * 0.2 = 147
assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
.isWithin(PRECISION).of(147);
- assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
UidBatteryConsumer uid1 = mStatsRule.getUidBatteryConsumer(APP_UID1);
assertThat(uid1.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
@@ -375,8 +346,6 @@ public class ScreenPowerCalculatorTest {
// Uid1 charge = 20 / 110 * 147.0 = 23.0 mAh
assertThat(uid1.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
.isWithin(PRECISION).of(26.72727);
- assertThat(uid1.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
UidBatteryConsumer uid2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
assertThat(uid2.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
@@ -386,17 +355,12 @@ public class ScreenPowerCalculatorTest {
// Uid2 charge = 90 / 110 * 92.0 = 69.0 mAh
assertThat(uid2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
.isWithin(PRECISION).of(120.272727);
- assertThat(uid2.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
assertThat(appsConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
.isEqualTo(110 * MINUTE_IN_MS);
assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
.isWithin(PRECISION).of(147);
- assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
-
}
private void setProcState(int uid, int procState, boolean resumed, long realtimeMs,
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerCalculatorTest.java
index 8e221be261e9..827d2f8f04c8 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerCalculatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerCalculatorTest.java
@@ -159,22 +159,16 @@ public class WifiPowerCalculatorTest {
.isEqualTo(2473);
assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI))
.isWithin(PRECISION).of(0.3964);
- assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
assertThat(deviceConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_WIFI))
.isEqualTo(4001);
assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI))
.isWithin(PRECISION).of(0.86666);
- assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
BatteryConsumer appsConsumer = mStatsRule.getDeviceBatteryConsumer();
assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI))
.isWithin(PRECISION).of(0.866666);
- assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
}
@Test
@@ -214,8 +208,6 @@ public class WifiPowerCalculatorTest {
.isEqualTo(12423);
assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI))
.isWithin(PRECISION).of(2.0214666);
- assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
final BatteryConsumer.Key foreground = uidConsumer.getKey(
BatteryConsumer.POWER_COMPONENT_WIFI,
@@ -248,22 +240,16 @@ public class WifiPowerCalculatorTest {
/* Same ratio as in testPowerControllerBasedModel_nonMeasured but scaled by 1_000_000uC. */
assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI))
.isWithin(PRECISION).of(0.2214666 / (0.2214666 + 0.645200) * 1_000_000 / 3600000);
- assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
assertThat(deviceConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_WIFI))
.isEqualTo(4002);
assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI))
.isWithin(PRECISION).of(0.27777);
- assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
BatteryConsumer appsConsumer = mStatsRule.getDeviceBatteryConsumer();
assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI))
.isWithin(PRECISION).of(0.277777);
- assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
}
@Test
@@ -302,8 +288,6 @@ public class WifiPowerCalculatorTest {
.isEqualTo(12423);
assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI))
.isWithin(PRECISION).of(1.0325211);
- assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
final BatteryConsumer.Key foreground = uidConsumer.getKey(
BatteryConsumer.POWER_COMPONENT_WIFI,
@@ -349,8 +333,6 @@ public class WifiPowerCalculatorTest {
.isEqualTo(1000);
assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI))
.isWithin(PRECISION).of(0.8231573);
- assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI))
- .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
}
@Test
@@ -371,8 +353,6 @@ public class WifiPowerCalculatorTest {
/* Same ratio as in testTimerBasedModel_nonMeasured but scaled by 1_000_000uC. */
assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI))
.isWithin(PRECISION).of(0.8231573 / (0.8231573 + 0.8759216) * 1_000_000 / 3600000);
- assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI))
- .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
}
private WifiActivityEnergyInfo buildWifiActivityEnergyInfo(long timeSinceBoot,
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/BasePowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/BasePowerStatsProcessorTest.java
index f7a16383974d..cca60339acf7 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/BasePowerStatsProcessorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/BasePowerStatsProcessorTest.java
@@ -176,7 +176,6 @@ public class BasePowerStatsProcessorTest {
powerStatsAggregator, /* batterySessionTimeSpanSlackMillis */ 0);
BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(new String[0],
- /* includePowerModels */ false,
/* includeProcessStateData */ true,
/* includeScreenStateData */ true,
/* includesPowerStateData */ true,
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/PowerStatsExporterTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/PowerStatsExporterTest.java
index 4643dddb1a33..38fe6134d992 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/processor/PowerStatsExporterTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/processor/PowerStatsExporterTest.java
@@ -342,7 +342,7 @@ public class PowerStatsExporterTest {
PowerStatsExporter exporter = new PowerStatsExporter(mPowerStatsStore,
mPowerStatsAggregator, /* batterySessionTimeSpanSlackMillis */ 0);
- BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(new String[0], false,
+ BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(new String[0],
includeProcessStateData, includeScreenStateData, includesPowerStateData, 0);
exporter.populateBatteryUsageStatsBuilder(builder, aps);
return builder.build();
@@ -361,7 +361,7 @@ public class PowerStatsExporterTest {
private void breakdownByProcState_fullRange(boolean includeScreenStateData,
boolean includePowerStateData) throws Exception {
BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(
- new String[]{"cu570m"}, /* includePowerModels */ false,
+ new String[]{"cu570m"},
/* includeProcessStateData */ true, includeScreenStateData,
includePowerStateData, /* powerThreshold */ 0);
exportAggregatedPowerStats(builder, 1000, 10000);
@@ -406,7 +406,7 @@ public class PowerStatsExporterTest {
@Test
public void breakdownByProcState_subRange() throws Exception {
BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(
- new String[]{"cu570m"}, /* includePowerModels */ false,
+ new String[]{"cu570m"},
/* includeProcessStateData */ true, true, true, /* powerThreshold */ 0);
exportAggregatedPowerStats(builder, 3700, 6700);
@@ -438,7 +438,7 @@ public class PowerStatsExporterTest {
@Test
public void combinedProcessStates() throws Exception {
BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(
- new String[]{"cu570m"}, /* includePowerModels */ false,
+ new String[]{"cu570m"},
/* includeProcessStateData */ false, true, true, /* powerThreshold */ 0);
exportAggregatedPowerStats(builder, 1000, 10000);
diff --git a/services/tests/security/intrusiondetection/AndroidManifest.xml b/services/tests/security/intrusiondetection/AndroidManifest.xml
index f388e7ea8590..39e41cddb662 100644
--- a/services/tests/security/intrusiondetection/AndroidManifest.xml
+++ b/services/tests/security/intrusiondetection/AndroidManifest.xml
@@ -18,10 +18,19 @@
package="com.android.server.security.intrusiondetection.tests">
<uses-permission android:name="android.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING" />
- <uses-permission android:name="android.permission.MANAGE_DEVICE_ADMINS" />
+ <uses-permission android:name="android.permission.INTERNET"/>
- <application android:testOnly="true">
+ <application android:testOnly="true" android:debuggable="true" android:usesCleartextTraffic="true">
<uses-library android:name="android.test.runner"/>
+ <receiver android:name="com.android.server.security.intrusiondetection.IntrusionDetectionAdminReceiver"
+ android:permission="android.permission.BIND_DEVICE_ADMIN"
+ android:exported="true">
+ <meta-data android:name="android.app.device_admin"
+ android:resource="@xml/device_admin"/>
+ <intent-filter>
+ <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
+ </intent-filter>
+ </receiver>
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/services/tests/security/intrusiondetection/res/xml/device_admin.xml b/services/tests/security/intrusiondetection/res/xml/device_admin.xml
new file mode 100644
index 000000000000..f8cd8f0b9b44
--- /dev/null
+++ b/services/tests/security/intrusiondetection/res/xml/device_admin.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<device-admin xmlns:android="http://schemas.android.com/apk/res/android">
+</device-admin>
diff --git a/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/IntrusionDetectionServiceTest.java b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/IntrusionDetectionServiceTest.java
index bc854cf6488b..c185ad5ffd61 100644
--- a/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/IntrusionDetectionServiceTest.java
+++ b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/IntrusionDetectionServiceTest.java
@@ -16,14 +16,19 @@
package com.android.server.security.intrusiondetection;
+import static android.Manifest.permission.INTERNET;
import static android.Manifest.permission.MANAGE_INTRUSION_DETECTION_STATE;
import static android.Manifest.permission.READ_INTRUSION_DETECTION_STATE;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
@@ -33,7 +38,9 @@ import static org.mockito.Mockito.verify;
import android.annotation.SuppressLint;
import android.app.admin.ConnectEvent;
import android.app.admin.DnsEvent;
+import android.app.admin.SecurityLog;
import android.app.admin.SecurityLog.SecurityEvent;
+import android.content.ComponentName;
import android.content.Context;
import android.os.Looper;
import android.os.PermissionEnforcer;
@@ -43,19 +50,44 @@ import android.os.test.TestLooper;
import android.security.intrusiondetection.IIntrusionDetectionServiceCommandCallback;
import android.security.intrusiondetection.IIntrusionDetectionServiceStateCallback;
import android.security.intrusiondetection.IntrusionDetectionEvent;
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyProperties;
import androidx.test.core.app.ApplicationProvider;
+import com.android.bedstead.harrier.BedsteadJUnit4;
+import com.android.bedstead.harrier.annotations.AfterClass;
+import com.android.bedstead.harrier.annotations.BeforeClass;
+import com.android.bedstead.multiuser.annotations.RequireRunOnSystemUser;
+import com.android.bedstead.nene.TestApis;
+import com.android.bedstead.nene.devicepolicy.DeviceOwner;
+import com.android.bedstead.nene.exceptions.NeneException;
+import com.android.bedstead.permissions.CommonPermissions;
+import com.android.bedstead.permissions.PermissionContext;
+import com.android.bedstead.permissions.annotations.EnsureHasPermission;
import com.android.server.ServiceThread;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
+import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
-
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.KeyStore;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+@RunWith(BedsteadJUnit4.class)
public class IntrusionDetectionServiceTest {
private static final int STATE_UNKNOWN =
IIntrusionDetectionServiceStateCallback.State.UNKNOWN;
@@ -73,6 +105,8 @@ public class IntrusionDetectionServiceTest {
private static final int ERROR_DATA_SOURCE_UNAVAILABLE =
IIntrusionDetectionServiceCommandCallback.ErrorCode.DATA_SOURCE_UNAVAILABLE;
+ private static DeviceOwner sDeviceOwner;
+
private Context mContext;
private IntrusionDetectionEventTransportConnection mIntrusionDetectionEventTransportConnection;
private DataAggregator mDataAggregator;
@@ -83,6 +117,28 @@ public class IntrusionDetectionServiceTest {
private Looper mLooperOfDataAggregator;
private FakePermissionEnforcer mPermissionEnforcer;
+ @BeforeClass
+ public static void setDeviceOwner() {
+ ComponentName admin =
+ new ComponentName(
+ ApplicationProvider.getApplicationContext(),
+ IntrusionDetectionAdminReceiver.class);
+ try {
+ sDeviceOwner = TestApis.devicePolicy().setDeviceOwner(admin);
+ } catch (NeneException e) {
+ fail("Failed to set device owner " + admin.flattenToString() + ": " + e);
+ }
+ }
+
+ @AfterClass
+ public static void removeDeviceOwner() {
+ try {
+ sDeviceOwner.remove();
+ } catch (NeneException e) {
+ fail("Failed to remove device owner : " + e);
+ }
+ }
+
@SuppressLint("VisibleForTests")
@Before
public void setUp() {
@@ -343,6 +399,172 @@ public class IntrusionDetectionServiceTest {
assertNotNull(receivedEvents.get(2).getDnsEvent());
}
+ @Test
+ @RequireRunOnSystemUser
+ public void testDataSources_Initialize_HasDeviceOwner() throws Exception {
+ NetworkLogSource networkLogSource = new NetworkLogSource(mContext, mDataAggregator);
+ SecurityLogSource securityLogSource = new SecurityLogSource(mContext, mDataAggregator);
+
+ assertTrue(networkLogSource.initialize());
+ assertTrue(securityLogSource.initialize());
+ }
+
+ @Test
+ @RequireRunOnSystemUser
+ public void testDataSources_Initialize_NoDeviceOwner() throws Exception {
+ NetworkLogSource networkLogSource = new NetworkLogSource(mContext, mDataAggregator);
+ SecurityLogSource securityLogSource = new SecurityLogSource(mContext, mDataAggregator);
+ ComponentName admin = sDeviceOwner.componentName();
+
+ try {
+ sDeviceOwner.remove();
+ assertFalse(networkLogSource.initialize());
+ assertFalse(securityLogSource.initialize());
+ } finally {
+ sDeviceOwner = TestApis.devicePolicy().setDeviceOwner(admin);
+ }
+ }
+
+ @Test
+ @RequireRunOnSystemUser
+ @EnsureHasPermission(CommonPermissions.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
+ public void testDataAggregator_AddSecurityEvent() throws Exception {
+ mIntrusionDetectionService.setState(STATE_ENABLED);
+ ServiceThread mockThread = spy(ServiceThread.class);
+ mDataAggregator.setHandler(mLooperOfDataAggregator, mockThread);
+ assertTrue(mDataAggregator.initialize());
+
+ // SecurityLogging generates a number of events and callbacks, so create a latch to wait for
+ // the given event.
+ String eventString = this.getClass().getName() + ".testSecurityEvent";
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ // TODO: Replace this mock when the IntrusionDetectionEventTransportConnection is ready.
+ doAnswer(
+ new Answer<Boolean>() {
+ @Override
+ public Boolean answer(InvocationOnMock input) {
+ List<IntrusionDetectionEvent> receivedEvents =
+ (List<IntrusionDetectionEvent>) input.getArguments()[0];
+ for (IntrusionDetectionEvent event : receivedEvents) {
+ if (event.getType() == IntrusionDetectionEvent.SECURITY_EVENT) {
+ SecurityEvent securityEvent = event.getSecurityEvent();
+ Object[] eventData = (Object[]) securityEvent.getData();
+ if (securityEvent.getTag() == SecurityLog.TAG_KEY_GENERATED
+ && eventData[1].equals(eventString)) {
+ latch.countDown();
+ }
+ }
+ }
+ return true;
+ }
+ })
+ .when(mIntrusionDetectionEventTransportConnection).addData(any());
+ mDataAggregator.enable();
+
+ // Generate the security event.
+ generateSecurityEvent(eventString);
+ TestApis.devicePolicy().forceSecurityLogs();
+
+ // Verify the event is received.
+ mTestLooper.startAutoDispatch();
+ assertTrue(latch.await(1, TimeUnit.SECONDS));
+ mTestLooper.stopAutoDispatch();
+
+ mDataAggregator.disable();
+ }
+
+ @Test
+ @RequireRunOnSystemUser
+ @EnsureHasPermission(CommonPermissions.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
+ public void testDataAggregator_AddNetworkEvent() throws Exception {
+ mIntrusionDetectionService.setState(STATE_ENABLED);
+ ServiceThread mockThread = spy(ServiceThread.class);
+ mDataAggregator.setHandler(mLooperOfDataAggregator, mockThread);
+ assertTrue(mDataAggregator.initialize());
+
+ // Network logging may log multiple and callbacks, so create a latch to wait for
+ // the given event.
+ // eventServer must be a valid domain to generate a network log event.
+ String eventServer = "google.com";
+ final CountDownLatch latch = new CountDownLatch(1);
+ // TODO: Replace this mock when the IntrusionDetectionEventTransportConnection is ready.
+ doAnswer(
+ new Answer<Boolean>() {
+ @Override
+ public Boolean answer(InvocationOnMock input) {
+ List<IntrusionDetectionEvent> receivedEvents =
+ (List<IntrusionDetectionEvent>) input.getArguments()[0];
+ for (IntrusionDetectionEvent event : receivedEvents) {
+ if (event.getType()
+ == IntrusionDetectionEvent.NETWORK_EVENT_DNS) {
+ DnsEvent dnsEvent = event.getDnsEvent();
+ if (dnsEvent.getHostname().equals(eventServer)) {
+ latch.countDown();
+ }
+ }
+ }
+ return true;
+ }
+ })
+ .when(mIntrusionDetectionEventTransportConnection).addData(any());
+ mDataAggregator.enable();
+
+ // Generate the network event.
+ generateNetworkEvent(eventServer);
+ TestApis.devicePolicy().forceNetworkLogs();
+
+ // Verify the event is received.
+ mTestLooper.startAutoDispatch();
+ assertTrue(latch.await(1, TimeUnit.SECONDS));
+ mTestLooper.stopAutoDispatch();
+
+ mDataAggregator.disable();
+ }
+
+ /** Emits a given string into security log (if enabled). */
+ private void generateSecurityEvent(String eventString)
+ throws IllegalArgumentException, GeneralSecurityException, IOException {
+ if (eventString == null || eventString.isEmpty()) {
+ throw new IllegalArgumentException(
+ "Error generating security event: eventString must not be empty");
+ }
+
+ final KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore");
+ keyGen.initialize(
+ new KeyGenParameterSpec.Builder(eventString, KeyProperties.PURPOSE_SIGN).build());
+ // Emit key generation event.
+ final KeyPair keyPair = keyGen.generateKeyPair();
+ assertNotNull(keyPair);
+
+ final KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
+ ks.load(null);
+ // Emit key destruction event.
+ ks.deleteEntry(eventString);
+ }
+
+ /** Emits a given string into network log (if enabled). */
+ private void generateNetworkEvent(String server) throws IllegalArgumentException, IOException {
+ if (server == null || server.isEmpty()) {
+ throw new IllegalArgumentException(
+ "Error generating network event: server must not be empty");
+ }
+
+ HttpURLConnection urlConnection = null;
+ int connectionTimeoutMS = 2_000;
+ try (PermissionContext p = TestApis.permissions().withPermission(INTERNET)) {
+ final URL url = new URL("http://" + server);
+ urlConnection = (HttpURLConnection) url.openConnection();
+ urlConnection.setConnectTimeout(connectionTimeoutMS);
+ urlConnection.setReadTimeout(connectionTimeoutMS);
+ urlConnection.getResponseCode();
+ } finally {
+ if (urlConnection != null) {
+ urlConnection.disconnect();
+ }
+ }
+ }
+
private class MockInjector implements IntrusionDetectionService.Injector {
private final Context mContext;
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 492838e9b4fb..27de7644f6b2 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -38,7 +38,6 @@ import static com.android.internal.accessibility.common.ShortcutConstants.UserSh
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
import static com.android.internal.accessibility.dialog.AccessibilityButtonChooserActivity.EXTRA_TYPE_TO_CHOOSE;
import static com.android.server.accessibility.AccessibilityManagerService.ACTION_LAUNCH_HEARING_DEVICES_DIALOG;
-import static com.android.window.flags.Flags.FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER;
import static com.google.common.truth.Truth.assertThat;
@@ -601,7 +600,6 @@ public class AccessibilityManagerServiceTest {
}
@Test
- @EnableFlags(FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER)
public void testSetConnectionNull_borderFlagEnabled_unregisterFullScreenMagnification()
throws RemoteException {
mFakePermissionEnforcer.grant(Manifest.permission.STATUS_BAR_SERVICE);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
index 7e0c12a5a545..ac27a971102a 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
@@ -21,7 +21,6 @@ import static android.accessibilityservice.MagnificationConfig.MAGNIFICATION_MOD
import static com.android.server.accessibility.Flags.FLAG_MAGNIFICATION_ENLARGE_POINTER_BUGFIX;
import static com.android.server.accessibility.magnification.FullScreenMagnificationController.MagnificationInfoChangedCallback;
import static com.android.server.accessibility.magnification.MockMagnificationConnection.TEST_DISPLAY;
-import static com.android.window.flags.Flags.FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -308,7 +307,6 @@ public class FullScreenMagnificationControllerTest {
}
@Test
- @RequiresFlagsEnabled(FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER)
public void testSetScale_noConnection_doNothing() {
register(TEST_DISPLAY);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
index f0d3456a39de..c878799109dc 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
@@ -57,10 +57,6 @@ import android.hardware.display.DisplayManagerInternal;
import android.os.Looper;
import android.os.RemoteException;
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.test.mock.MockContentResolver;
import android.testing.DexmakerShareClassLoaderRule;
@@ -82,7 +78,6 @@ import com.android.server.accessibility.AccessibilityTraceManager;
import com.android.server.accessibility.test.MessageCapturingHandler;
import com.android.server.input.InputManagerInternal;
import com.android.server.wm.WindowManagerInternal;
-import com.android.window.flags.Flags;
import org.junit.After;
import org.junit.Before;
@@ -101,9 +96,6 @@ import org.mockito.stubbing.Answer;
@RunWith(AndroidJUnit4.class)
public class MagnificationControllerTest {
- @Rule
- public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
-
private static final int TEST_DISPLAY = Display.DEFAULT_DISPLAY;
private static final int TEST_SERVICE_ID = 1;
private static final Region INITIAL_SCREEN_MAGNIFICATION_REGION =
@@ -1365,8 +1357,7 @@ public class MagnificationControllerTest {
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER)
- public void onFullscreenMagnificationActivationState_systemUiBorderFlagOn_notifyConnection() {
+ public void onFullscreenMagnificationActivationState_notifyConnection() {
mMagnificationController.onFullScreenMagnificationActivationState(
TEST_DISPLAY, /* activated= */ true);
@@ -1374,17 +1365,6 @@ public class MagnificationControllerTest {
.onFullscreenMagnificationActivationChanged(TEST_DISPLAY, /* activated= */ true);
}
- @Test
- @RequiresFlagsDisabled(Flags.FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER)
- public void
- onFullscreenMagnificationActivationState_systemUiBorderFlagOff_neverNotifyConnection() {
- mMagnificationController.onFullScreenMagnificationActivationState(
- TEST_DISPLAY, /* activated= */ true);
-
- verify(mMagnificationConnectionManager, never())
- .onFullscreenMagnificationActivationChanged(TEST_DISPLAY, /* activated= */ true);
- }
-
private void setMagnificationEnabled(int mode) throws RemoteException {
setMagnificationEnabled(mode, MAGNIFIED_CENTER_X, MAGNIFIED_CENTER_Y);
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index a7bf3e194dd9..30aa8cebdff6 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -5036,8 +5036,6 @@ public class DevicePolicyManagerTest extends DpmTestBase {
@Test
@RequiresFlagsEnabled(Flags.FLAG_SECONDARY_LOCKSCREEN_API_ENABLED)
public void testSetSecondaryLockscreenEnabled() throws Exception {
- mContext.binder.callingUid = DpmMockContext.CALLER_UID;
-
verifySetSecondaryLockscreenEnabled(false);
verifySetSecondaryLockscreenEnabled(true);
}
@@ -5045,6 +5043,10 @@ public class DevicePolicyManagerTest extends DpmTestBase {
private void verifySetSecondaryLockscreenEnabled(boolean enabled) throws Exception {
reset(getServices().supervisionManagerInternal);
+ mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+ doReturn(DpmMockContext.CALLER_UID).when(getServices().packageManagerInternal)
+ .getPackageUid(any(), anyLong(), anyInt());
+
dpm.setSecondaryLockscreenEnabled(admin1, enabled);
verify(getServices().supervisionManagerInternal).setSupervisionLockscreenEnabledForUser(
CALLER_USER_HANDLE, enabled, null);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index 0816e7b61165..5be4490e67ef 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -118,6 +118,7 @@ public class HdmiCecLocalDeviceTvTest {
private boolean mDisableCecOnStandbyByLowEnergyMode;
private boolean mWasCecDisabledOnStandbyByLowEnergyMode;
private boolean mUseHdmiCecPowerStatusController;
+ private boolean mUserEnabledCecInOfflineMode;
private class DeviceEventListener {
private HdmiDeviceInfo mDevice;
@@ -250,6 +251,11 @@ public class HdmiCecLocalDeviceTvTest {
protected void setWasCecDisabledOnStandbyByLowEnergyMode(boolean value) {
mWasCecDisabledOnStandbyByLowEnergyMode = value;
}
+
+ @Override
+ protected boolean userEnabledCecInOfflineMode() {
+ return mUserEnabledCecInOfflineMode;
+ }
};
mHdmiControlService.setIoLooper(mMyLooper);
@@ -298,6 +304,7 @@ public class HdmiCecLocalDeviceTvTest {
mWasCecDisabledOnStandbyByLowEnergyMode = false;
mDisableCecOnStandbyByLowEnergyMode = false;
mUseHdmiCecPowerStatusController = false;
+ mUserEnabledCecInOfflineMode = false;
mNativeWrapper.clearResultMessages();
}
@@ -2400,6 +2407,32 @@ public class HdmiCecLocalDeviceTvTest {
assertTrue(mVendorCommandListeners.contains(vendorCommandListenerInvocationSettingChange));
}
+ @Test
+ public void lowEnergyMode_userEnabledCecInOfflineMode_onStandby_cecStaysEnabled() {
+ mDisableCecOnStandbyByLowEnergyMode = true;
+ mUseHdmiCecPowerStatusController = true;
+ mUserEnabledCecInOfflineMode = true;
+ mPowerManager.setIsLowPowerStandbyEnabled(true);
+
+ assertEquals(mHdmiCecLocalDeviceTv.mService.getHdmiCecConfig().getIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED),
+ HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+ mHdmiControlService.onStandby(STANDBY_SCREEN_OFF);
+ mTestLooper.dispatchAll();
+
+ assertEquals(mHdmiCecLocalDeviceTv.mService.getHdmiCecConfig().getIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED),
+ HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+ assertFalse(mWasCecDisabledOnStandbyByLowEnergyMode);
+ mHdmiControlService.onWakeUp(WAKE_UP_SCREEN_ON);
+ mTestLooper.dispatchAll();
+
+ assertEquals(mHdmiCecLocalDeviceTv.mService.getHdmiCecConfig().getIntValue(
+ HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED),
+ HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+ assertFalse(mWasCecDisabledOnStandbyByLowEnergyMode);
+ }
+
protected static class MockTvDevice extends HdmiCecLocalDeviceTv {
MockTvDevice(HdmiControlService service) {
super(service);
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java
index d5ed048032e7..1d138e4a48d9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java
@@ -249,6 +249,18 @@ public class AppCompatCameraOverridesTest extends WindowTestsBase {
}
@Test
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
+ public void testShouldApplyCameraCompatFreeformTreatment_enabledByShellCommand_returnsTrue() {
+ runTestScenario((robot) -> {
+ robot.activity().createActivityWithComponentInNewTask();
+
+ robot.setCameraCompatTreatmentEnabledViaShellCommand(true);
+
+ robot.checkShouldApplyFreeformTreatmentForCameraCompat(true);
+ });
+ }
+
+ @Test
@EnableCompatChanges({OVERRIDE_ORIENTATION_ONLY_FOR_CAMERA,
OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR_CAMERA,
OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT})
@@ -350,6 +362,11 @@ public class AppCompatCameraOverridesTest extends WindowTestsBase {
spyOn(displayContent.mAppCompatCameraPolicy);
}
+ void setCameraCompatTreatmentEnabledViaShellCommand(boolean enabled) {
+ activity().top().mWmService.mAppCompatConfiguration
+ .setIsCameraCompatFreeformWindowingTreatmentEnabled(enabled);
+ }
+
void checkShouldRefreshActivityForCameraCompat(boolean expected) {
Assert.assertEquals(getAppCompatCameraOverrides()
.shouldRefreshActivityForCameraCompat(), expected);
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 4fbe492ea7fb..584c4c96ae1d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -4917,7 +4917,8 @@ public class SizeCompatTests extends WindowTestsBase {
assertEquals(minAspect, aspectRatioPolicy.getMinAspectRatio(), 0 /* delta */);
// User override can still take effect.
- doReturn(true).when(aspectRatioOverrides).shouldApplyUserMinAspectRatioOverride();
+ doReturn(USER_MIN_ASPECT_RATIO_3_2).when(aspectRatioOverrides)
+ .getUserMinAspectRatioOverrideCode();
assertFalse(mActivity.isResizeable());
assertEquals(maxAspect, aspectRatioPolicy.getMaxAspectRatio(), 0 /* delta */);
assertNotEquals(SCREEN_ORIENTATION_UNSPECIFIED, mActivity.getOverrideOrientation());
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index 8bbba1b17240..a425401c6238 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -42,7 +42,6 @@ import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static android.view.flags.Flags.FLAG_SENSITIVE_CONTENT_APP_PROTECTION;
import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
@@ -85,7 +84,6 @@ import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.platform.test.annotations.Presubmit;
-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;
@@ -97,7 +95,6 @@ import android.view.InputChannel;
import android.view.InputDevice;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
-import android.view.Surface;
import android.view.SurfaceControl;
import android.view.View;
import android.view.WindowInsets;
@@ -1192,55 +1189,6 @@ public class WindowManagerServiceTests extends WindowTestsBase {
argThat(h -> (h.inputConfig & InputConfig.SENSITIVE_FOR_PRIVACY) != 0));
}
- @RequiresFlagsDisabled(Flags.FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER)
- @Test
- public void testDrawMagnifiedViewport() {
- final int displayId = mDisplayContent.mDisplayId;
- // Use real surface, so ViewportWindow's BlastBufferQueue can be created.
- final ArrayList<SurfaceControl> surfaceControls = new ArrayList<>();
- mWm.mSurfaceControlFactory = () -> new SurfaceControl.Builder() {
- @Override
- public SurfaceControl build() {
- final SurfaceControl sc = super.build();
- surfaceControls.add(sc);
- return sc;
- }
- };
- mWm.mAccessibilityController.setMagnificationCallbacks(displayId,
- mock(WindowManagerInternal.MagnificationCallbacks.class));
- final boolean[] lockCanvasInWmLock = { false };
- final Surface surface = mWm.mAccessibilityController.forceShowMagnifierSurface(displayId);
- spyOn(surface);
- doAnswer(invocationOnMock -> {
- lockCanvasInWmLock[0] |= Thread.holdsLock(mWm.mGlobalLock);
- invocationOnMock.callRealMethod();
- return null;
- }).when(surface).lockCanvas(any());
- mWm.mAccessibilityController
- .recomputeMagnifiedRegionAndDrawMagnifiedRegionBorderIfNeeded(displayId);
- waitUntilHandlersIdle();
- try {
- verify(surface).lockCanvas(any());
-
- clearInvocations(surface);
- // Invalidate and redraw.
- mWm.mAccessibilityController.onDisplaySizeChanged(mDisplayContent);
- mWm.mAccessibilityController
- .recomputeMagnifiedRegionAndDrawMagnifiedRegionBorderIfNeeded(displayId);
- // Turn off magnification to release surface.
- mWm.mAccessibilityController.setMagnificationCallbacks(displayId, null);
- waitUntilHandlersIdle();
- // lockCanvas must not be called after releasing.
- verify(surface, never()).lockCanvas(any());
- verify(surface).release();
- assertFalse(lockCanvasInWmLock[0]);
- } finally {
- for (int i = surfaceControls.size() - 1; i >= 0; --i) {
- surfaceControls.get(i).release();
- }
- }
- }
-
@Test
public void testRequestKeyboardShortcuts_noWindow() {
doNothing().when(mWm.mContext).enforceCallingOrSelfPermission(anyString(), anyString());
diff --git a/telephony/java/android/telephony/satellite/ISelectedNbIotSatelliteSubscriptionCallback.aidl b/telephony/java/android/telephony/satellite/ISelectedNbIotSatelliteSubscriptionCallback.aidl
new file mode 100644
index 000000000000..178bb3c29ea3
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/ISelectedNbIotSatelliteSubscriptionCallback.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.satellite;
+
+/**
+ * Interface for selected satellite subscription change callback.
+ *
+ * @hide
+ */
+oneway interface ISelectedNbIotSatelliteSubscriptionCallback {
+ /**
+ * Called when the selected satellite subscription has changed.
+ *
+ * @param selectedSubId The new satellite subscription id.
+ */
+ void onSelectedNbIotSatelliteSubscriptionChanged(in int selectedSubId);
+}
diff --git a/telephony/java/android/telephony/satellite/SatelliteInfo.java b/telephony/java/android/telephony/satellite/SatelliteInfo.java
index 7ff231812c8a..1d5f613cc086 100644
--- a/telephony/java/android/telephony/satellite/SatelliteInfo.java
+++ b/telephony/java/android/telephony/satellite/SatelliteInfo.java
@@ -17,7 +17,6 @@
package android.telephony.satellite;
import android.annotation.FlaggedApi;
-import android.annotation.Nullable;
import android.os.Parcel;
import android.os.ParcelUuid;
import android.os.Parcelable;
@@ -47,10 +46,12 @@ public class SatelliteInfo implements Parcelable {
private UUID mId;
/**
- * Position information of a satellite.
+ * Position information of a geostationary satellite.
* This includes the longitude and altitude of the satellite.
+ * If the SatellitePosition is invalid,
+ * longitudeDegree and altitudeKm will be represented as DOUBLE.NaN.
*/
- @Nullable
+ @NonNull
private SatellitePosition mPosition;
/**
@@ -89,7 +90,7 @@ public class SatelliteInfo implements Parcelable {
* @param earfcnRanges The list of {@link EarfcnRange} objects representing the EARFCN
* ranges supported by the satellite.
*/
- public SatelliteInfo(@NonNull UUID satelliteId, @Nullable SatellitePosition satellitePosition,
+ public SatelliteInfo(@NonNull UUID satelliteId, @NonNull SatellitePosition satellitePosition,
@NonNull List<Integer> bandList, @NonNull List<EarfcnRange> earfcnRanges) {
mId = satelliteId;
mPosition = satellitePosition;
@@ -135,10 +136,9 @@ public class SatelliteInfo implements Parcelable {
/**
* Returns the position of the satellite.
*
- * @return The {@link SatellitePosition} of the satellite, or {@code null} if the position is
- * not available.
+ * @return The {@link SatellitePosition} of the satellite.
*/
- @Nullable
+ @NonNull
public SatellitePosition getSatellitePosition() {
return mPosition;
}
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 23203ed65e9a..5a34b0038872 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -99,16 +99,18 @@ public final class SatelliteManager {
private static final ConcurrentHashMap<SatelliteSupportedStateCallback,
ISatelliteSupportedStateCallback> sSatelliteSupportedStateCallbackMap =
new ConcurrentHashMap<>();
-
private static final ConcurrentHashMap<SatelliteCommunicationAllowedStateCallback,
ISatelliteCommunicationAllowedStateCallback>
sSatelliteCommunicationAllowedStateCallbackMap =
new ConcurrentHashMap<>();
-
private static final ConcurrentHashMap<SatelliteDisallowedReasonsCallback,
ISatelliteDisallowedReasonsCallback>
sSatelliteDisallowedReasonsCallbackMap =
new ConcurrentHashMap<>();
+ private static final ConcurrentHashMap<SelectedNbIotSatelliteSubscriptionCallback,
+ ISelectedNbIotSatelliteSubscriptionCallback>
+ sSelectedNbIotSatelliteSubscriptionCallbackMap =
+ new ConcurrentHashMap<>();
private final int mSubId;
@@ -746,7 +748,7 @@ public final class SatelliteManager {
* @hide
*/
public static final String ACTION_SATELLITE_SUBSCRIBER_ID_LIST_CHANGED =
- "android.telephony.action.ACTION_SATELLITE_SUBSCRIBER_ID_LIST_CHANGED";
+ "android.telephony.satellite.action.SATELLITE_SUBSCRIBER_ID_LIST_CHANGED";
/**
@@ -757,7 +759,7 @@ public final class SatelliteManager {
* @hide
*/
public static final String ACTION_SATELLITE_START_NON_EMERGENCY_SESSION =
- "android.telephony.action.ACTION_SATELLITE_START_NON_EMERGENCY_SESSION";
+ "android.telephony.satellite.action.SATELLITE_START_NON_EMERGENCY_SESSION";
/**
* Meta-data represents whether the application supports P2P SMS over carrier roaming satellite
* which needs manual trigger to connect to satellite. The messaging applications that supports
@@ -2541,6 +2543,89 @@ public final class SatelliteManager {
}
/**
+ * Registers for selected satellite subscription changed event from the satellite service.
+ *
+ * @param executor The executor on which the callback will be called.
+ * @param callback The callback to handle the selected satellite subscription changed event.
+ *
+ * @throws SecurityException if the caller doesn't have required permission.
+ * @throws IllegalStateException if the Telephony process is not currently available.
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ @SatelliteResult public int registerForSelectedNbIotSatelliteSubscriptionChanged(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull SelectedNbIotSatelliteSubscriptionCallback callback) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ ISelectedNbIotSatelliteSubscriptionCallback internalCallback =
+ new ISelectedNbIotSatelliteSubscriptionCallback.Stub() {
+ @Override
+ public void onSelectedNbIotSatelliteSubscriptionChanged(
+ int selectedSubId) {
+ executor.execute(() -> Binder.withCleanCallingIdentity(
+ () -> callback.onSelectedNbIotSatelliteSubscriptionChanged(
+ selectedSubId)));
+ }
+ };
+ sSelectedNbIotSatelliteSubscriptionCallbackMap.put(callback, internalCallback);
+ return telephony.registerForSelectedNbIotSatelliteSubscriptionChanged(
+ internalCallback);
+ } else {
+ throw new IllegalStateException("Telephony service is null.");
+ }
+ } catch (RemoteException ex) {
+ loge("registerForSelectedNbIotSatelliteSubscriptionChanged() RemoteException: " + ex);
+ ex.rethrowFromSystemServer();
+ }
+ return SATELLITE_RESULT_REQUEST_FAILED;
+ }
+
+ /**
+ * Unregisters for selected satellite subscription changed event from the satellite service. If
+ * callback was not registered before, the request will be ignored.
+ *
+ * @param callback The callback that was passed to {@link
+ * #registerForSelectedNbIotSatelliteSubscriptionChanged(Executor,
+ * SelectedNbIotSatelliteSubscriptionCallback)}.
+ *
+ * @throws SecurityException if the caller doesn't have required permission.
+ * @throws IllegalStateException if the Telephony process is not currently available.
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ public void unregisterForSelectedNbIotSatelliteSubscriptionChanged(
+ @NonNull SelectedNbIotSatelliteSubscriptionCallback callback) {
+ Objects.requireNonNull(callback);
+ ISelectedNbIotSatelliteSubscriptionCallback internalCallback =
+ sSelectedNbIotSatelliteSubscriptionCallbackMap.remove(callback);
+
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ if (internalCallback != null) {
+ telephony.unregisterForSelectedNbIotSatelliteSubscriptionChanged(
+ internalCallback);
+ } else {
+ loge("unregisterForSelectedNbIotSatelliteSubscriptionChanged: " +
+ "No internal callback.");
+ }
+ } else {
+ throw new IllegalStateException("Telephony service is null.");
+ }
+ } catch (RemoteException ex) {
+ loge("unregisterForSelectedNbIotSatelliteSubscriptionChanged() RemoteException: " +
+ ex);
+ ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Inform whether the device is aligned with the satellite in both real and demo mode.
*
* In demo mode, framework will send datagram to modem only when device is aligned with
diff --git a/telephony/java/android/telephony/satellite/SelectedNbIotSatelliteSubscriptionCallback.java b/telephony/java/android/telephony/satellite/SelectedNbIotSatelliteSubscriptionCallback.java
new file mode 100644
index 000000000000..d8965547a20e
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/SelectedNbIotSatelliteSubscriptionCallback.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.satellite;
+
+/**
+ * A callback class for selected satellite subscription changed events.
+ *
+ * @hide
+ */
+public interface SelectedNbIotSatelliteSubscriptionCallback {
+ /**
+ * Called when the selected satellite subscription has changed.
+ *
+ * @param selectedSubId The new satellite subscription id.
+ */
+ void onSelectedNbIotSatelliteSubscriptionChanged(int selectedSubId);
+}
diff --git a/telephony/java/android/telephony/satellite/SystemSelectionSpecifier.java b/telephony/java/android/telephony/satellite/SystemSelectionSpecifier.java
index 8a5e7f22888a..c2ac44f0067e 100644
--- a/telephony/java/android/telephony/satellite/SystemSelectionSpecifier.java
+++ b/telephony/java/android/telephony/satellite/SystemSelectionSpecifier.java
@@ -17,11 +17,13 @@
package android.telephony.satellite;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
import android.util.IntArray;
+import java.util.Arrays;
import java.util.Objects;
/**
@@ -39,16 +41,26 @@ public final class SystemSelectionSpecifier implements Parcelable {
* The radio channels to scan as defined in 3GPP TS 25.101 and 36.101.
* Maximum length of the vector is 32.
*/
- @NonNull private IntArray mEarfcs;
+ @NonNull private IntArray mEarfcns;
+
+ /* The list of satellites configured for the current location */
+ @Nullable
+ private SatelliteInfo[] mSatelliteInfos;
+
+ /* The list of tag IDs associated with the current location */
+ @Nullable private IntArray mTagIds;
/**
* @hide
*/
public SystemSelectionSpecifier(@NonNull String mccmnc, @NonNull IntArray bands,
- @NonNull IntArray earfcs) {
+ @NonNull IntArray earfcns, @Nullable SatelliteInfo[] satelliteInfos,
+ @Nullable IntArray tagIds) {
mMccMnc = mccmnc;
mBands = bands;
- mEarfcs = earfcs;
+ mEarfcns = earfcns;
+ mSatelliteInfos = satelliteInfos;
+ mTagIds = tagIds;
}
private SystemSelectionSpecifier(Parcel in) {
@@ -74,10 +86,21 @@ public final class SystemSelectionSpecifier implements Parcelable {
out.writeInt(0);
}
- if (mEarfcs != null && mEarfcs.size() > 0) {
- out.writeInt(mEarfcs.size());
- for (int i = 0; i < mEarfcs.size(); i++) {
- out.writeInt(mEarfcs.get(i));
+ if (mEarfcns != null && mEarfcns.size() > 0) {
+ out.writeInt(mEarfcns.size());
+ for (int i = 0; i < mEarfcns.size(); i++) {
+ out.writeInt(mEarfcns.get(i));
+ }
+ } else {
+ out.writeInt(0);
+ }
+
+ out.writeTypedArray(mSatelliteInfos, flags);
+
+ if (mTagIds != null) {
+ out.writeInt(mTagIds.size());
+ for (int i = 0; i < mTagIds.size(); i++) {
+ out.writeInt(mTagIds.get(i));
}
} else {
out.writeInt(0);
@@ -115,14 +138,35 @@ public final class SystemSelectionSpecifier implements Parcelable {
}
sb.append("earfcs:");
- if (mEarfcs != null && mEarfcs.size() > 0) {
- for (int i = 0; i < mEarfcs.size(); i++) {
- sb.append(mEarfcs.get(i));
+ if (mEarfcns != null && mEarfcns.size() > 0) {
+ for (int i = 0; i < mEarfcns.size(); i++) {
+ sb.append(mEarfcns.get(i));
sb.append(",");
}
} else {
sb.append("none");
}
+
+ sb.append("mSatelliteInfos:");
+ if (mSatelliteInfos != null && mSatelliteInfos.length > 0) {
+ for (SatelliteInfo satelliteInfo : mSatelliteInfos) {
+ sb.append(satelliteInfo);
+ sb.append(",");
+ }
+ } else {
+ sb.append("none");
+ }
+
+ sb.append("mTagIds:");
+ if (mTagIds != null && mTagIds.size() > 0) {
+ for (int i = 0; i < mTagIds.size(); i++) {
+ sb.append(mTagIds.get(i));
+ sb.append(",");
+ }
+ } else {
+ sb.append("none");
+ }
+
return sb.toString();
}
@@ -133,12 +177,15 @@ public final class SystemSelectionSpecifier implements Parcelable {
SystemSelectionSpecifier that = (SystemSelectionSpecifier) o;
return Objects.equals(mMccMnc, that.mMccMnc)
&& Objects.equals(mBands, that.mBands)
- && Objects.equals(mEarfcs, that.mEarfcs);
+ && Objects.equals(mEarfcns, that.mEarfcns)
+ && (mSatelliteInfos == null ? that.mSatelliteInfos == null : Arrays.equals(
+ mSatelliteInfos, that.mSatelliteInfos))
+ && Objects.equals(mTagIds, that.mTagIds);
}
@Override
public int hashCode() {
- return Objects.hash(mMccMnc, mBands, mEarfcs);
+ return Objects.hash(mMccMnc, mBands, mEarfcns);
}
@NonNull public String getMccMnc() {
@@ -149,8 +196,18 @@ public final class SystemSelectionSpecifier implements Parcelable {
return mBands;
}
- @NonNull public IntArray getEarfcs() {
- return mEarfcs;
+ @NonNull public IntArray getEarfcns() {
+ return mEarfcns;
+ }
+
+ @NonNull
+ public SatelliteInfo[] getSatelliteInfos() {
+ return mSatelliteInfos;
+ }
+
+ @NonNull
+ public IntArray getTagIds() {
+ return mTagIds;
}
private void readFromParcel(Parcel in) {
@@ -164,11 +221,20 @@ public final class SystemSelectionSpecifier implements Parcelable {
}
}
- mEarfcs = new IntArray();
- int numEarfcs = in.readInt();
- if (numEarfcs > 0) {
- for (int i = 0; i < numEarfcs; i++) {
- mEarfcs.add(in.readInt());
+ mEarfcns = new IntArray();
+ int numEarfcns = in.readInt();
+ if (numEarfcns > 0) {
+ for (int i = 0; i < numEarfcns; i++) {
+ mEarfcns.add(in.readInt());
+ }
+ }
+
+ mSatelliteInfos = in.createTypedArray(SatelliteInfo.CREATOR);
+
+ int numTagIds = in.readInt();
+ if (numTagIds > 0) {
+ for (int i = 0; i < numTagIds; i++) {
+ mTagIds.add(in.readInt());
}
}
}
diff --git a/telephony/java/android/telephony/satellite/stub/EarfcnRange.aidl b/telephony/java/android/telephony/satellite/stub/EarfcnRange.aidl
new file mode 100644
index 000000000000..6e9d19e91fc5
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/stub/EarfcnRange.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.satellite.stub;
+
+/**
+ * @hide
+ */
+parcelable EarfcnRange {
+ /**
+ * The start frequency of the earfcn range and is inclusive in the range
+ */
+ int startEarfcn;
+
+ /**
+ * The end frequency of the earfcn range and is inclusive in the range.
+ */
+ int endEarfcn;
+}
diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteInfo.aidl b/telephony/java/android/telephony/satellite/stub/SatelliteInfo.aidl
new file mode 100644
index 000000000000..312bd8f5b2af
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/stub/SatelliteInfo.aidl
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.satellite.stub;
+
+import android.telephony.satellite.stub.UUID;
+import android.telephony.satellite.stub.SatellitePosition;
+import android.telephony.satellite.stub.EarfcnRange;
+
+/**
+ * @hide
+ */
+parcelable SatelliteInfo {
+ /**
+ * Unique identification number for the satellite.
+ * This ID is used to distinguish between different satellites in the network.
+ */
+ UUID id;
+
+ /**
+ * Position information of a geostationary satellite.
+ * This includes the longitude and altitude of the satellite.
+ * If the SatellitePosition is invalid,
+ * longitudeDegree and altitudeKm will be represented as DOUBLE.NaN.
+ */
+ SatellitePosition position;
+
+ /**
+ * The frequency bands to scan.
+ * Bands will be filled only if the whole band is needed.
+ * Maximum length of the vector is 8.
+ */
+ int[] bands;
+
+ /**
+ * The supported frequency ranges. Earfcn ranges and earfcns won't overlap.
+ */
+ EarfcnRange[] earfcnRanges;
+}
diff --git a/telephony/java/android/telephony/satellite/stub/SatellitePosition.aidl b/telephony/java/android/telephony/satellite/stub/SatellitePosition.aidl
new file mode 100644
index 000000000000..e87b26cddec5
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/stub/SatellitePosition.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.satellite.stub;
+
+/**
+ * @hide
+ */
+parcelable SatellitePosition {
+ /**
+ * The longitude of the satellite in degrees, ranging from -180 to 180 degrees
+ */
+ double longitudeDegree;
+
+ /**
+ * The distance from the center of the earth to the satellite, measured in kilometers
+ */
+ double altitudeKm;
+}
diff --git a/telephony/java/android/telephony/satellite/stub/SystemSelectionSpecifier.aidl b/telephony/java/android/telephony/satellite/stub/SystemSelectionSpecifier.aidl
index 22240f6324c9..3aff4cb63167 100644
--- a/telephony/java/android/telephony/satellite/stub/SystemSelectionSpecifier.aidl
+++ b/telephony/java/android/telephony/satellite/stub/SystemSelectionSpecifier.aidl
@@ -16,6 +16,8 @@
package android.telephony.satellite.stub;
+import android.telephony.satellite.stub.SatelliteInfo;
+
/**
* {@hide}
*/
@@ -24,15 +26,26 @@ parcelable SystemSelectionSpecifier {
String mMccMnc;
/**
- * The frequency bands to scan. Bands and earfcns won't overlap.
+ * The frequency bands to scan.
* Bands will be filled only if the whole band is needed.
* Maximum length of the vector is 8.
+ * The values are populated from the mBands array within the SatelliteInfo[] array, which is
+ * included in the SystemSelectionSpecifier, for backward compatibility.
*/
int[] mBands;
/**
* The radio channels to scan as defined in 3GPP TS 25.101 and 36.101.
+ * The values are populated from the earfcns defined in the EarfcnRange[] array inside
+ * SatelliteInfo[], which is included in the SystemSelectionSpecifier, for backward
+ * compatibility.
* Maximum length of the vector is 32.
*/
int[] mEarfcs;
+
+ /* The list of satellites configured for the current location */
+ SatelliteInfo[] satelliteInfos;
+
+ /* The list of tag IDs associated with the current location */
+ int[] tagIds;
}
diff --git a/telephony/java/android/telephony/satellite/stub/UUID.aidl b/telephony/java/android/telephony/satellite/stub/UUID.aidl
new file mode 100644
index 000000000000..004a507bb16e
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/stub/UUID.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.satellite.stub;
+
+/**
+ * @hide
+ */
+parcelable UUID {
+ /*
+ * The most significant 64 bits of this UUID.
+ */
+ long mostSigBits;
+
+ /*
+ * The least significant 64 bits of this UUID.
+ */
+ long leastSigBits;
+}
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 294c93c8e493..b739666c3f1b 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -76,6 +76,7 @@ import android.telephony.satellite.ISatelliteTransmissionUpdateCallback;
import android.telephony.satellite.ISatelliteProvisionStateCallback;
import android.telephony.satellite.ISatelliteSupportedStateCallback;
import android.telephony.satellite.ISatelliteModemStateCallback;
+import android.telephony.satellite.ISelectedNbIotSatelliteSubscriptionCallback;
import android.telephony.satellite.NtnSignalStrength;
import android.telephony.satellite.SatelliteCapabilities;
import android.telephony.satellite.SatelliteDatagram;
@@ -3030,6 +3031,30 @@ interface ITelephony {
void requestSelectedNbIotSatelliteSubscriptionId(in ResultReceiver receiver);
/**
+ * Registers for selected satellite subscription changed event from the satellite service.
+ *
+ * @param executor The executor on which the callback will be called.
+ * @param callback The callback to handle the satellite subscription changed event.
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+ int registerForSelectedNbIotSatelliteSubscriptionChanged(
+ in ISelectedNbIotSatelliteSubscriptionCallback callback);
+
+ /**
+ * Unregisters for selected satellite subscription changed event from the satellite service. If
+ * callback was not registered before, the request will be ignored.
+ *
+ * @param callback The callback that was passed to {@link
+ * #registerForSelectedNbIotSatelliteSubscriptionChanged(Executor,
+ * SelectedNbIotSatelliteSubscriptionCallback)}.
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+ void unregisterForSelectedNbIotSatelliteSubscriptionChanged(
+ in ISelectedNbIotSatelliteSubscriptionCallback callback);
+
+ /**
* Inform whether the device is aligned with the satellite in both real and demo mode.
*
* @param isAligned {@true} Device is aligned with the satellite.
diff --git a/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java b/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java
index bfce3d276a2d..6d818d7287b0 100644
--- a/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java
+++ b/tests/BatteryStatsPerfTest/src/com/android/internal/os/BatteryUsageStatsPerfTest.java
@@ -159,7 +159,7 @@ public class BatteryUsageStatsPerfTest {
private static BatteryUsageStats buildBatteryUsageStats() {
final BatteryUsageStats.Builder builder =
- new BatteryUsageStats.Builder(new String[]{"FOO"}, true, false, false, false, 0)
+ new BatteryUsageStats.Builder(new String[]{"FOO"}, false, false, false, 0)
.setBatteryCapacity(4000)
.setDischargePercentage(20)
.setDischargedPowerRange(1000, 2000)
@@ -182,8 +182,7 @@ public class BatteryUsageStatsPerfTest {
.setTimeInProcessStateMs(UidBatteryConsumer.STATE_BACKGROUND, i * 1000);
for (int componentId = 0; componentId < BatteryConsumer.POWER_COMPONENT_COUNT;
componentId++) {
- consumerBuilder.addConsumedPower(componentId, componentId * 123.0,
- BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+ consumerBuilder.addConsumedPower(componentId, componentId * 123.0);
consumerBuilder.addUsageDurationMillis(componentId, componentId * 1000);
}
diff --git a/tools/systemfeatures/Android.bp b/tools/systemfeatures/Android.bp
index e6d0a3d4149f..2ebede39504e 100644
--- a/tools/systemfeatures/Android.bp
+++ b/tools/systemfeatures/Android.bp
@@ -13,6 +13,7 @@ java_library_host {
srcs: [
"src/**/*.java",
"src/**/*.kt",
+ ":framework-metalava-annotations",
],
static_libs: [
"guava",
@@ -26,6 +27,12 @@ java_binary_host {
static_libs: ["systemfeatures-gen-lib"],
}
+java_plugin {
+ name: "systemfeatures-metadata-processor",
+ processor_class: "com.android.systemfeatures.SystemFeaturesMetadataProcessor",
+ static_libs: ["systemfeatures-gen-lib"],
+}
+
genrule {
name: "systemfeatures-gen-tests-srcs",
cmd: "$(location systemfeatures-gen-tool) com.android.systemfeatures.RwNoFeatures --readonly=false > $(location RwNoFeatures.java) && " +
@@ -61,6 +68,7 @@ java_test_host {
"systemfeatures-gen-lib",
"truth",
],
+ plugins: ["systemfeatures-metadata-processor"],
}
// Rename the goldens as they may be copied into the source tree, and we don't
diff --git a/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesMetadataProcessor.kt b/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesMetadataProcessor.kt
new file mode 100644
index 000000000000..100d869a663f
--- /dev/null
+++ b/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesMetadataProcessor.kt
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemfeatures
+
+import android.annotation.SdkConstant
+import com.squareup.javapoet.FieldSpec
+import com.squareup.javapoet.JavaFile
+import com.squareup.javapoet.TypeSpec
+import java.io.IOException
+import javax.annotation.processing.AbstractProcessor
+import javax.annotation.processing.ProcessingEnvironment
+import javax.annotation.processing.RoundEnvironment
+import javax.lang.model.SourceVersion
+import javax.lang.model.element.Modifier
+import javax.lang.model.element.TypeElement
+import javax.tools.Diagnostic
+
+/*
+ * Simple Java code generator for computing metadata for system features.
+ *
+ * <p>The output is a single class file, `com.android.internal.pm.SystemFeaturesMetadata`, with
+ * properties computed from feature constant definitions in the PackageManager class. This
+ * class is only produced if the processed environment includes PackageManager; all other
+ * invocations are ignored.
+ */
+class SystemFeaturesMetadataProcessor : AbstractProcessor() {
+
+ private lateinit var packageManagerType: TypeElement
+
+ override fun getSupportedSourceVersion(): SourceVersion = SourceVersion.latestSupported()
+
+ override fun getSupportedAnnotationTypes() = setOf(SDK_CONSTANT_ANNOTATION_NAME)
+
+ override fun init(processingEnv: ProcessingEnvironment) {
+ super.init(processingEnv)
+ packageManagerType =
+ processingEnv.elementUtils.getTypeElement("android.content.pm.PackageManager")!!
+ }
+
+ override fun process(annotations: Set<TypeElement>, roundEnv: RoundEnvironment): Boolean {
+ if (roundEnv.processingOver()) {
+ return false
+ }
+
+ // We're only interested in feature constants defined in PackageManager.
+ var featureCount = 0
+ roundEnv.getElementsAnnotatedWith(SdkConstant::class.java).forEach {
+ if (
+ it.enclosingElement == packageManagerType &&
+ it.getAnnotation(SdkConstant::class.java).value ==
+ SdkConstant.SdkConstantType.FEATURE
+ ) {
+ featureCount++
+ }
+ }
+
+ if (featureCount == 0) {
+ // This is fine, and happens for any environment that doesn't include PackageManager.
+ return false
+ }
+
+ val systemFeatureMetadata =
+ TypeSpec.classBuilder("SystemFeaturesMetadata")
+ .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
+ .addJavadoc("@hide")
+ .addField(
+ FieldSpec.builder(Int::class.java, "SDK_FEATURE_COUNT")
+ .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
+ .addJavadoc(
+ "The number of `@SdkConstant` features defined in PackageManager."
+ )
+ .addJavadoc("@hide")
+ .initializer("\$L", featureCount)
+ .build()
+ )
+ .build()
+
+ try {
+ JavaFile.builder("com.android.internal.pm", systemFeatureMetadata)
+ .skipJavaLangImports(true)
+ .build()
+ .writeTo(processingEnv.filer)
+ } catch (e: IOException) {
+ processingEnv.messager.printMessage(
+ Diagnostic.Kind.ERROR,
+ "Failed to write file: ${e.message}",
+ )
+ }
+
+ return true
+ }
+
+ companion object {
+ private val SDK_CONSTANT_ANNOTATION_NAME = SdkConstant::class.qualifiedName
+ }
+}
diff --git a/tools/systemfeatures/tests/src/PackageManager.java b/tools/systemfeatures/tests/src/PackageManager.java
index db670482065a..839a9377476d 100644
--- a/tools/systemfeatures/tests/src/PackageManager.java
+++ b/tools/systemfeatures/tests/src/PackageManager.java
@@ -16,14 +16,33 @@
package android.content.pm;
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+
/** Stub for testing */
public class PackageManager {
+ @SdkConstant(SdkConstantType.FEATURE)
public static final String FEATURE_AUTO = "automotive";
+
+ @SdkConstant(SdkConstantType.FEATURE)
public static final String FEATURE_PC = "pc";
+
+ @SdkConstant(SdkConstantType.FEATURE)
public static final String FEATURE_VULKAN = "vulkan";
+
+ @SdkConstant(SdkConstantType.FEATURE)
public static final String FEATURE_WATCH = "watch";
+
+ @SdkConstant(SdkConstantType.FEATURE)
public static final String FEATURE_WIFI = "wifi";
+ @SdkConstant(SdkConstantType.INTENT_CATEGORY)
+ public static final String FEATURE_INTENT_CATEGORY = "intent_category_with_feature_name_prefix";
+
+ public static final String FEATURE_NOT_ANNOTATED = "not_annotated";
+
+ public static final String NOT_FEATURE = "not_feature";
+
/** @hide */
public boolean hasSystemFeature(String featureName, int version) {
return false;
diff --git a/tools/systemfeatures/tests/src/SystemFeaturesMetadataProcessorTest.java b/tools/systemfeatures/tests/src/SystemFeaturesMetadataProcessorTest.java
new file mode 100644
index 000000000000..4ffb5b979d75
--- /dev/null
+++ b/tools/systemfeatures/tests/src/SystemFeaturesMetadataProcessorTest.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemfeatures;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.internal.pm.SystemFeaturesMetadata;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class SystemFeaturesMetadataProcessorTest {
+
+ @Test
+ public void testSdkFeatureCount() {
+ // See the fake PackageManager definition in this directory.
+ // It defines 5 annotated features, and any/all other constants should be ignored.
+ assertThat(SystemFeaturesMetadata.SDK_FEATURE_COUNT).isEqualTo(5);
+ }
+}