summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java6
-rw-r--r--core/api/current.txt57
-rw-r--r--core/api/system-current.txt13
-rw-r--r--core/api/test-current.txt10
-rw-r--r--core/java/Android.bp38
-rw-r--r--core/java/android/app/ActivityOptions.java44
-rw-r--r--core/java/android/app/DownloadManager.java7
-rw-r--r--core/java/android/app/Notification.java842
-rw-r--r--core/java/android/app/SystemServiceRegistry.java7
-rw-r--r--core/java/android/app/WallpaperManager.java8
-rw-r--r--core/java/android/app/admin/PolicySizeVerifier.java11
-rw-r--r--core/java/android/app/appfunctions/AppFunctionManager.java235
-rw-r--r--core/java/android/app/appfunctions/AppFunctionManagerHelper.java160
-rw-r--r--core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java10
-rw-r--r--core/java/android/app/appfunctions/AppFunctionService.java64
-rw-r--r--core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java4
-rw-r--r--core/java/android/app/appfunctions/GenericDocumentWrapper.java95
-rw-r--r--core/java/android/app/appfunctions/IAppFunctionEnabledCallback.aidl27
-rw-r--r--core/java/android/app/appfunctions/IAppFunctionManager.aidl18
-rw-r--r--core/java/android/app/appfunctions/IAppFunctionService.aidl4
-rw-r--r--core/java/android/app/appfunctions/ICancellationCallback.aidl24
-rw-r--r--core/java/android/app/wallpaper.aconfig8
-rw-r--r--core/java/android/content/pm/flags.aconfig9
-rw-r--r--core/java/android/hardware/camera2/CameraManager.java10
-rw-r--r--core/java/android/hardware/soundtrigger/SoundTrigger.java87
-rw-r--r--core/java/android/provider/Settings.java10
-rw-r--r--core/java/android/ranging/mock/RangingFrameworkInitializer.java34
-rw-r--r--core/java/android/security/responsible_apis_flags.aconfig11
-rw-r--r--core/java/android/service/notification/ZenModeConfig.java9
-rw-r--r--core/java/android/view/OWNERS1
-rw-r--r--core/java/android/view/ViewRootImpl.java24
-rw-r--r--core/java/android/view/WindowLayout.java17
-rw-r--r--core/java/android/view/accessibility/AccessibilityWindowInfo.java10
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java26
-rw-r--r--core/java/android/window/flags/windowing_frontend.aconfig8
-rw-r--r--core/java/com/android/internal/pm/pkg/component/ParsedPermissionUtils.java16
-rw-r--r--core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java6
-rw-r--r--core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java7
-rw-r--r--core/java/com/android/internal/protolog/Utils.java6
-rw-r--r--core/proto/android/server/vibrator/vibratormanagerservice.proto4
-rw-r--r--core/res/res/values-bg/strings.xml4
-rw-r--r--core/res/res/values-bs/strings.xml2
-rw-r--r--core/res/res/values-fa/strings.xml4
-rw-r--r--core/res/res/values-fi/strings.xml2
-rw-r--r--core/res/res/values-kn/strings.xml126
-rw-r--r--core/res/res/values-ky/strings.xml6
-rw-r--r--core/res/res/values-ms/strings.xml2
-rw-r--r--core/res/res/values-my/strings.xml2
-rw-r--r--core/res/res/values-ne/strings.xml2
-rw-r--r--core/res/res/values-pt-rBR/strings.xml4
-rw-r--r--core/res/res/values-pt/strings.xml4
-rw-r--r--core/res/res/values-sw/strings.xml6
-rw-r--r--core/res/res/values-th/strings.xml2
-rw-r--r--core/res/res/values/config.xml9
-rw-r--r--core/res/res/values/dimens.xml6
-rw-r--r--core/res/res/values/public-staging.xml3
-rw-r--r--core/res/res/values/symbols.xml5
-rw-r--r--core/tests/coretests/src/android/app/NotificationTest.java324
-rw-r--r--core/tests/coretests/src/android/view/WindowLayoutTests.java15
-rw-r--r--graphics/java/android/graphics/PathIterator.java19
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java270
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/BackupHelper.java14
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java33
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java6
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java14
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/Android.bp2
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducerTest.kt341
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java3
-rw-r--r--libs/WindowManager/Shell/aconfig/multitasking.aconfig7
-rw-r--r--libs/WindowManager/Shell/res/drawable/app_handle_education_tooltip_icon.xml27
-rw-r--r--libs/WindowManager/Shell/res/drawable/desktop_windowing_education_tooltip_background.xml25
-rw-r--r--libs/WindowManager/Shell/res/drawable/desktop_windowing_education_tooltip_left_arrow.xml27
-rw-r--r--libs/WindowManager/Shell/res/drawable/desktop_windowing_education_tooltip_top_arrow.xml26
-rw-r--r--libs/WindowManager/Shell/res/layout/desktop_windowing_education_left_arrow_tooltip.xml36
-rw-r--r--libs/WindowManager/Shell/res/layout/desktop_windowing_education_tooltip_container.xml43
-rw-r--r--libs/WindowManager/Shell/res/layout/desktop_windowing_education_top_arrow_tooltip.xml35
-rw-r--r--libs/WindowManager/Shell/res/layout/letterbox_restart_dialog_layout.xml8
-rw-r--r--libs/WindowManager/Shell/res/values-bg/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-bn/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-cs/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-en-rAU/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-en-rCA/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-en-rGB/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-en-rIN/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-en-rXC/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-fa/strings_tv.xml2
-rw-r--r--libs/WindowManager/Shell/res/values-hi/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-hu/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-ja/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-ka/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-lo/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-lt/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-ml/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-ms/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-pa/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-pl/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-pt-rPT/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-ru/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-sl/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-sv/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values-uz/strings.xml12
-rw-r--r--libs/WindowManager/Shell/res/values/dimen.xml3
-rw-r--r--libs/WindowManager/Shell/res/values/strings.xml12
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/FocusTransitionListener.java30
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/IFocusTransitionListener.aidl28
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/IShellTransitions.aidl6
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/ShellTransitions.java13
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/OWNERS4
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitScreenConstants.java85
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt29
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/FocusTransitionObserver.java142
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java132
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java42
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt23
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/education/DesktopWindowingEducationTooltipController.kt249
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/AndroidTestTemplate.xml4
-rw-r--r--libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/AndroidTestTemplate.xml4
-rw-r--r--libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/AndroidTestTemplate.xml4
-rw-r--r--libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/AndroidTestTemplate.xml4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt183
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt138
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt6
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java18
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/SplitBoundsTest.java14
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/split/SplitScreenConstantsTest.kt37
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/FocusTransitionObserverTest.java155
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java12
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/education/DesktopWindowingEducationTooltipControllerTest.kt237
-rw-r--r--libs/appfunctions/api/current.txt12
-rw-r--r--libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java142
-rw-r--r--libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java37
-rw-r--r--libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java4
-rw-r--r--libs/hwui/SkiaCanvas.cpp5
-rw-r--r--libs/hwui/hwui/MinikinSkia.cpp6
-rw-r--r--libs/hwui/hwui/MinikinSkia.h8
-rw-r--r--libs/hwui/hwui/Typeface.cpp9
-rw-r--r--libs/hwui/hwui/Typeface.h4
-rw-r--r--libs/hwui/jni/FontFamily.cpp6
-rw-r--r--libs/hwui/jni/PathIterator.cpp14
-rw-r--r--libs/hwui/jni/Typeface.cpp5
-rw-r--r--libs/hwui/jni/fonts/Font.cpp4
-rw-r--r--media/java/android/media/tv/flags/media_tv.aconfig8
-rw-r--r--media/jni/android_media_ImageWriter.cpp14
-rw-r--r--native/android/system_fonts.cpp2
-rw-r--r--nfc/api/system-current.txt5
-rw-r--r--nfc/java/android/nfc/INfcAdapter.aidl3
-rw-r--r--nfc/java/android/nfc/NfcOemExtension.java52
-rw-r--r--packages/SettingsLib/ActionButtonsPreference/res/layout-v35/settingslib_expressive_action_buttons.xml24
-rw-r--r--packages/SettingsLib/ActionButtonsPreference/src/com/android/settingslib/widget/ActionButtonsPreference.java10
-rw-r--r--packages/SettingsLib/Android.bp3
-rw-r--r--packages/SettingsLib/AppPreference/res/layout-v33/preference_app.xml9
-rw-r--r--packages/SettingsLib/AppPreference/res/layout/preference_app.xml9
-rw-r--r--packages/SettingsLib/AppPreference/src/com/android/settingslib/widget/AppPreference.java31
-rw-r--r--packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled.xml29
-rw-r--r--packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled_extra.xml29
-rw-r--r--packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled_large.xml29
-rw-r--r--packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline.xml29
-rw-r--r--packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline_extra.xml29
-rw-r--r--packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline_large.xml29
-rw-r--r--packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal.xml29
-rw-r--r--packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal_extra.xml29
-rw-r--r--packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal_large.xml29
-rw-r--r--packages/SettingsLib/ButtonPreference/res/values-v35/attrs_expressive.xml31
-rw-r--r--packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java65
-rw-r--r--packages/SettingsLib/CardPreference/Android.bp33
-rw-r--r--packages/SettingsLib/CardPreference/AndroidManifest.xml23
-rw-r--r--packages/SettingsLib/CardPreference/res/layout/settingslib_expressive_preference_card.xml88
-rw-r--r--packages/SettingsLib/CardPreference/res/values/styles_expressive.xml30
-rw-r--r--packages/SettingsLib/CardPreference/src/com/android/settingslib/widget/CardPreference.kt58
-rw-r--r--packages/SettingsLib/ExpandablePreference/Android.bp33
-rw-r--r--packages/SettingsLib/ExpandablePreference/AndroidManifest.xml23
-rw-r--r--packages/SettingsLib/ExpandablePreference/res/drawable/settingslib_ic_expand.xml37
-rw-r--r--packages/SettingsLib/ExpandablePreference/res/layout/settingslib_widget_expandable_icon.xml29
-rw-r--r--packages/SettingsLib/ExpandablePreference/src/com/android/settingslib/widget/ExpandablePreference.kt95
-rw-r--r--packages/SettingsLib/IntroPreference/Android.bp33
-rw-r--r--packages/SettingsLib/IntroPreference/AndroidManifest.xml23
-rw-r--r--packages/SettingsLib/IntroPreference/res/layout/settingslib_expressive_preference_intro.xml45
-rw-r--r--packages/SettingsLib/IntroPreference/src/com/android/settingslib/widget/IntroPreference.kt102
-rw-r--r--packages/SettingsLib/Preference/Android.bp13
-rw-r--r--packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt7
-rw-r--r--packages/SettingsLib/Preference/testutils/com/android/settingslib/preference/CatalystScreenTestCase.kt123
-rw-r--r--packages/SettingsLib/SearchWidget/res/values-ms/strings.xml2
-rw-r--r--packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_collapse.xml36
-rw-r--r--packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_expand.xml37
-rw-r--r--packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_collapsable_textview.xml62
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-v35/attrs_expressive.xml25
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-v35/strings.xml24
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-v35/styles_expressive.xml148
-rw-r--r--packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/CollapsableTextView.kt208
-rw-r--r--packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/Expandable.kt21
-rw-r--r--packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/LinkableTextView.kt41
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt10
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/banner/BannerPageProvider.kt16
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonPageProvider.kt16
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/chart/ChartPageProvider.kt15
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/dialog/DialogMainPageProvider.kt15
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/EditorMainPageProvider.kt16
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt62
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/itemList/ItemListPage.kt99
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/itemList/ItemOperatePage.kt127
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/itemList/OperateListPage.kt56
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt125
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPageModel.kt138
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPageProvider.kt16
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/IllustrationPageProvider.kt (renamed from packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/IllustrationPage.kt)18
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/LoadingBarPageProvider.kt16
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ProgressBarPageProvider.kt (renamed from packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ProgressBarPage.kt)16
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPageProvider.kt17
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/IntroPreferencePageProvider.kt15
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/ListPreferencePageProvider.kt34
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/MainSwitchPreferencePageProvider.kt (renamed from packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/MainSwitchPreferencePage.kt)14
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMainPageProvider.kt53
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageModel.kt112
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageProvider.kt233
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/SwitchPreferencePageProvider.kt67
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TopIntroPreferencePageProvider.kt15
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetSwitchPreferencePageProvider.kt60
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/ZeroStatePreferencePageProvider.kt14
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/PagerMainPageProvider.kt16
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/SearchScaffoldPageProvider.kt18
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/SuwScaffoldPageProvider.kt18
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPageProvider.kt (renamed from packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPage.kt)32
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CopyablePageProvider.kt20
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/SpinnerPageProvider.kt16
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt5
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt2
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTypography.kt3
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/banner/SettingsBanner.kt5
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt5
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt12
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt37
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/IntroPreference.kt5
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ZeroStatePreference.kt3
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt36
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt80
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Category.kt74
-rw-r--r--packages/SettingsLib/TopIntroPreference/Android.bp5
-rw-r--r--packages/SettingsLib/TopIntroPreference/res/layout-v35/settingslib_expressive_top_intro.xml27
-rw-r--r--packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.java51
-rw-r--r--packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.kt140
-rw-r--r--packages/SettingsLib/res/values-af/strings.xml12
-rw-r--r--packages/SettingsLib/res/values-az/strings.xml12
-rw-r--r--packages/SettingsLib/res/values-be/strings.xml12
-rw-r--r--packages/SettingsLib/res/values-bg/strings.xml12
-rw-r--r--packages/SettingsLib/res/values-bn/strings.xml12
-rw-r--r--packages/SettingsLib/res/values-bs/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-ca/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-cs/strings.xml12
-rw-r--r--packages/SettingsLib/res/values-da/strings.xml12
-rw-r--r--packages/SettingsLib/res/values-de/strings.xml12
-rw-r--r--packages/SettingsLib/res/values-el/strings.xml16
-rw-r--r--packages/SettingsLib/res/values-es-rUS/strings.xml12
-rw-r--r--packages/SettingsLib/res/values-es/strings.xml12
-rw-r--r--packages/SettingsLib/res/values-et/strings.xml12
-rw-r--r--packages/SettingsLib/res/values-eu/strings.xml12
-rw-r--r--packages/SettingsLib/res/values-fa/strings.xml12
-rw-r--r--packages/SettingsLib/res/values-fi/strings.xml12
-rw-r--r--packages/SettingsLib/res/values-fr-rCA/strings.xml12
-rw-r--r--packages/SettingsLib/res/values-fr/strings.xml12
-rw-r--r--packages/SettingsLib/res/values-gl/strings.xml12
-rw-r--r--packages/SettingsLib/res/values-hu/strings.xml12
-rw-r--r--packages/SettingsLib/res/values-hy/strings.xml12
-rw-r--r--packages/SettingsLib/res/values-in/strings.xml12
-rw-r--r--packages/SettingsLib/res/values-is/strings.xml12
-rw-r--r--packages/SettingsLib/res/values-it/strings.xml12
-rw-r--r--packages/SettingsLib/res/values-iw/strings.xml12
-rw-r--r--packages/SettingsLib/res/values-kk/strings.xml12
-rw-r--r--packages/SettingsLib/res/values-kn/strings.xml12
-rw-r--r--packages/SettingsLib/res/values-ko/strings.xml12
-rw-r--r--packages/SettingsLib/res/values-ky/strings.xml12
-rw-r--r--packages/SettingsLib/res/values-lo/strings.xml12
-rw-r--r--packages/SettingsLib/res/values-lt/strings.xml12
-rw-r--r--packages/SettingsLib/res/values-lv/strings.xml12
-rw-r--r--packages/SettingsLib/res/values-mn/strings.xml12
-rw-r--r--packages/SettingsLib/res/values-my/strings.xml12
-rw-r--r--packages/SettingsLib/res/values-nb/strings.xml12
-rw-r--r--packages/SettingsLib/res/values-or/strings.xml12
-rw-r--r--packages/SettingsLib/res/values-pa/strings.xml12
-rw-r--r--packages/SettingsLib/res/values-pt-rPT/strings.xml12
-rw-r--r--packages/SettingsLib/res/values-ro/strings.xml12
-rw-r--r--packages/SettingsLib/res/values-sk/strings.xml12
-rw-r--r--packages/SettingsLib/res/values-sq/strings.xml12
-rw-r--r--packages/SettingsLib/res/values-sv/strings.xml12
-rw-r--r--packages/SettingsLib/res/values-sw/strings.xml12
-rw-r--r--packages/SettingsLib/res/values-ta/strings.xml12
-rw-r--r--packages/SettingsLib/res/values-tr/strings.xml12
-rw-r--r--packages/SettingsLib/res/values-uk/strings.xml12
-rw-r--r--packages/SettingsLib/res/values-uz/strings.xml12
-rw-r--r--packages/SettingsLib/res/values-vi/strings.xml12
-rw-r--r--packages/SettingsLib/res/values-zh-rCN/strings.xml12
-rw-r--r--packages/SettingsLib/res/values-zh-rHK/strings.xml12
-rw-r--r--packages/SettingsLib/res/values-zh-rTW/strings.xml12
-rw-r--r--packages/SettingsLib/res/values-zu/strings.xml12
-rw-r--r--packages/SettingsLib/res/values/strings.xml8
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java3
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java63
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingId.java4
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingItem.kt14
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfig.kt3
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfigServiceStatus.aidl19
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfigServiceStatus.kt60
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsProviderServiceStatus.kt2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/IDeviceSettingsConfigProviderService.aidl4
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/IGetDeviceSettingsConfigCallback.aidl24
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt91
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java19
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java15
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java2
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java106
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfigServiceStatusTest.kt51
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfigTest.kt1
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt38
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputMediaDeviceTest.java2
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java2
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AppPreferenceTest.java64
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java1
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java1
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig29
-rw-r--r--packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginRemoteTransition.java374
-rw-r--r--packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginTransitionSession.java40
-rw-r--r--packages/SystemUI/animation/lib/src/com/android/systemui/animation/SurfaceUIComponent.java169
-rw-r--r--packages/SystemUI/animation/lib/src/com/android/systemui/animation/Transactions.java86
-rw-r--r--packages/SystemUI/animation/lib/src/com/android/systemui/animation/UIComponent.java72
-rw-r--r--packages/SystemUI/animation/lib/src/com/android/systemui/animation/ViewUIComponent.java278
-rw-r--r--packages/SystemUI/compose/core/src/com/android/compose/PlatformButtons.kt24
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt146
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt25
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/dialog/ui/composable/AlertDialogContent.kt9
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt11
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt8
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt69
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java (renamed from packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java)6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModelTest.kt223
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/controls/start/ControlsStartableTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/controls/start/ControlsStartableTest.kt)6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt34
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/slider/SliderStateTrackerTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderStateTrackerTest.kt)13
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinatorTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinatorTest.kt)6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt)4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt)13
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt)4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt86
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt)4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt)4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModelTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt)4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt)2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt43
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelTest.kt30
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OffToLockscreenTransitionViewModelTest.kt94
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartableTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartableTest.kt)4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModelTest.kt89
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSFooterViewControllerTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java)4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSImplTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java)4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/AbstractQSFragmentComposeViewModelTest.kt69
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelForceQSTest.kt98
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt68
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayoutTest.kt7
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java36
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt)4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java)4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt95
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractorTest.kt131
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt89
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/IssueRecordingServiceSessionTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/IssueRecordingServiceCommandHandlerTest.kt)20
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt14
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerHapticsViewModelTest.kt233
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt21
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModelTest.kt16
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarSignalPolicyTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarSignalPolicyTest.kt)14
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt132
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt)4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorTest.kt11
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorTest.kt17
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModelTest.kt82
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt)22
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationSettingsControllerTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSettingsControllerTest.kt)11
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLoggerTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLoggerTest.kt)18
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerFlagDisabledTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerFlagDisabledTest.kt)4
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java5
-rw-r--r--packages/SystemUI/res/values-af/strings.xml11
-rw-r--r--packages/SystemUI/res/values-am/strings.xml5
-rw-r--r--packages/SystemUI/res/values-ar/strings.xml7
-rw-r--r--packages/SystemUI/res/values-as/strings.xml5
-rw-r--r--packages/SystemUI/res/values-az/strings.xml11
-rw-r--r--packages/SystemUI/res/values-b+sr+Latn/strings.xml5
-rw-r--r--packages/SystemUI/res/values-be/strings.xml11
-rw-r--r--packages/SystemUI/res/values-bg/strings.xml11
-rw-r--r--packages/SystemUI/res/values-bn/strings.xml11
-rw-r--r--packages/SystemUI/res/values-bs/strings.xml5
-rw-r--r--packages/SystemUI/res/values-ca/strings.xml5
-rw-r--r--packages/SystemUI/res/values-cs/strings.xml11
-rw-r--r--packages/SystemUI/res/values-da/strings.xml11
-rw-r--r--packages/SystemUI/res/values-de/strings.xml44
-rw-r--r--packages/SystemUI/res/values-el/strings.xml11
-rw-r--r--packages/SystemUI/res/values-en-rAU/strings.xml5
-rw-r--r--packages/SystemUI/res/values-en-rCA/strings.xml1
-rw-r--r--packages/SystemUI/res/values-en-rGB/strings.xml5
-rw-r--r--packages/SystemUI/res/values-en-rIN/strings.xml5
-rw-r--r--packages/SystemUI/res/values-en-rXC/strings.xml1
-rw-r--r--packages/SystemUI/res/values-es-rUS/strings.xml44
-rw-r--r--packages/SystemUI/res/values-es/strings.xml11
-rw-r--r--packages/SystemUI/res/values-et/strings.xml11
-rw-r--r--packages/SystemUI/res/values-eu/strings.xml11
-rw-r--r--packages/SystemUI/res/values-fa/strings.xml7
-rw-r--r--packages/SystemUI/res/values-fi/strings.xml11
-rw-r--r--packages/SystemUI/res/values-fr-rCA/strings.xml11
-rw-r--r--packages/SystemUI/res/values-fr/strings.xml11
-rw-r--r--packages/SystemUI/res/values-gl/strings.xml11
-rw-r--r--packages/SystemUI/res/values-gu/strings.xml5
-rw-r--r--packages/SystemUI/res/values-hi/strings.xml4
-rw-r--r--packages/SystemUI/res/values-hr/strings.xml5
-rw-r--r--packages/SystemUI/res/values-hu/strings.xml8
-rw-r--r--packages/SystemUI/res/values-hy/strings.xml11
-rw-r--r--packages/SystemUI/res/values-in/strings.xml11
-rw-r--r--packages/SystemUI/res/values-is/strings.xml11
-rw-r--r--packages/SystemUI/res/values-it/strings.xml11
-rw-r--r--packages/SystemUI/res/values-iw/strings.xml11
-rw-r--r--packages/SystemUI/res/values-ja/strings.xml5
-rw-r--r--packages/SystemUI/res/values-ka/strings.xml5
-rw-r--r--packages/SystemUI/res/values-kk/strings.xml11
-rw-r--r--packages/SystemUI/res/values-km/strings.xml5
-rw-r--r--packages/SystemUI/res/values-kn/strings.xml21
-rw-r--r--packages/SystemUI/res/values-ko/strings.xml11
-rw-r--r--packages/SystemUI/res/values-ky/strings.xml11
-rw-r--r--packages/SystemUI/res/values-lo/strings.xml11
-rw-r--r--packages/SystemUI/res/values-lt/strings.xml11
-rw-r--r--packages/SystemUI/res/values-lv/strings.xml11
-rw-r--r--packages/SystemUI/res/values-mk/strings.xml5
-rw-r--r--packages/SystemUI/res/values-ml/strings.xml5
-rw-r--r--packages/SystemUI/res/values-mn/strings.xml11
-rw-r--r--packages/SystemUI/res/values-mr/strings.xml5
-rw-r--r--packages/SystemUI/res/values-ms/strings.xml5
-rw-r--r--packages/SystemUI/res/values-my/strings.xml11
-rw-r--r--packages/SystemUI/res/values-nb/strings.xml11
-rw-r--r--packages/SystemUI/res/values-ne/strings.xml5
-rw-r--r--packages/SystemUI/res/values-nl/strings.xml5
-rw-r--r--packages/SystemUI/res/values-or/strings.xml11
-rw-r--r--packages/SystemUI/res/values-pa/strings.xml11
-rw-r--r--packages/SystemUI/res/values-pl/strings.xml5
-rw-r--r--packages/SystemUI/res/values-pt-rBR/strings.xml5
-rw-r--r--packages/SystemUI/res/values-pt-rPT/strings.xml8
-rw-r--r--packages/SystemUI/res/values-pt/strings.xml5
-rw-r--r--packages/SystemUI/res/values-ro/strings.xml11
-rw-r--r--packages/SystemUI/res/values-ru/strings.xml5
-rw-r--r--packages/SystemUI/res/values-si/strings.xml5
-rw-r--r--packages/SystemUI/res/values-sk/strings.xml23
-rw-r--r--packages/SystemUI/res/values-sl/strings.xml2
-rw-r--r--packages/SystemUI/res/values-sq/strings.xml11
-rw-r--r--packages/SystemUI/res/values-sr/strings.xml5
-rw-r--r--packages/SystemUI/res/values-sv/strings.xml11
-rw-r--r--packages/SystemUI/res/values-sw/strings.xml11
-rw-r--r--packages/SystemUI/res/values-ta/strings.xml11
-rw-r--r--packages/SystemUI/res/values-te/strings.xml7
-rw-r--r--packages/SystemUI/res/values-th/strings.xml4
-rw-r--r--packages/SystemUI/res/values-tl/strings.xml5
-rw-r--r--packages/SystemUI/res/values-tr/strings.xml11
-rw-r--r--packages/SystemUI/res/values-uk/strings.xml11
-rw-r--r--packages/SystemUI/res/values-ur/strings.xml5
-rw-r--r--packages/SystemUI/res/values-uz/strings.xml11
-rw-r--r--packages/SystemUI/res/values-vi/strings.xml11
-rw-r--r--packages/SystemUI/res/values-zh-rCN/strings.xml15
-rw-r--r--packages/SystemUI/res/values-zh-rCN/tiles_states_strings.xml4
-rw-r--r--packages/SystemUI/res/values-zh-rHK/strings.xml11
-rw-r--r--packages/SystemUI/res/values-zh-rTW/strings.xml15
-rw-r--r--packages/SystemUI/res/values-zu/strings.xml11
-rw-r--r--packages/SystemUI/res/values/dimens.xml2
-rw-r--r--packages/SystemUI/res/values/strings.xml2
-rw-r--r--packages/SystemUI/res/values/styles.xml6
-rw-r--r--packages/SystemUI/shared/Android.bp1
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/CoreStartable.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerHapticPlayer.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModel.kt83
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduDialog.kt64
-rw-r--r--packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduUiCoordinator.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduViewModel.kt55
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/haptics/msdl/MSDLCoreStartable.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialMetricsLogger.kt55
-rw-r--r--packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/TutorialNotificationCoordinator.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/view/KeyboardTouchpadTutorialActivity.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt91
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt57
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionBootInteractor.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt94
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/IntraBlueprintTransition.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToOccludedTransitionViewModel.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt29
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt44
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModel.kt49
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAlternateBouncerTransitionViewModel.kt49
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OffToLockscreenTransitionViewModel.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt117
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaActions.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt113
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListener.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt201
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHostStatesManager.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaHost.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java42
-rw-r--r--packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModel.kt42
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt141
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt60
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/TileSquishinessRepository.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/TileSquishinessInteractor.kt32
-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/infinitegrid/CommonTile.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/SquishTile.kt43
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileSquishinessViewModel.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java30
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java31
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt53
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractor.kt62
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/model/InternetTileModel.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModel.kt43
-rw-r--r--packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingServiceSession.kt (renamed from packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingServiceCommandHandler.kt)14
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/data/model/SceneStack.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneBackInteractor.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneUserActionsViewModel.kt51
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerHapticsViewModel.kt90
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java31
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActions.kt77
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModel.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java92
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/core/CommandQueueInitializer.kt114
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarConntectedDisplays.kt61
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarOrchestrator.kt248
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationActivityStarter.kt59
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinator.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorLogger.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/OriginalUnseenKeyguardCoordinator.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinator.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewbinder/EmptyShadeViewBinder.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModel.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt31
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationMinimalism.kt (renamed from packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationMinimalismPrototype.kt)10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java266
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java79
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt50
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/InternetTileIconModel.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt74
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/kotlin/Nullability.kt (renamed from packages/SystemUI/src/com/android/systemui/util/kotlin/nullability.kt)12
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualLocationsService.kt13
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java)0
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java41
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsViewTest.kt)0
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerPredictiveBackTest.kt25
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/FalsingDataProviderTest.java)0
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java18
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractorTest.kt103
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt18
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt19
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt33
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt186
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt40
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java29
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java26
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java22
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsReceiverTest.java10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerTest.kt9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/core/CommandQueueInitializerTest.kt129
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/core/StatusBarOrchestratorTest.kt335
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinatorTest.kt61
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt20
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt15
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt34
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerStartableTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FlashlightControllerImplTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RotationLockControllerImplTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/stylus/StylusManagerTest.kt15
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerStartableTest.kt5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTestActivity.java (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/BubblesTestActivity.java)0
-rw-r--r--packages/SystemUI/tests/utils/src/android/internal/statusbar/FakeStatusBarService.kt355
-rw-r--r--packages/SystemUI/tests/utils/src/android/internal/statusbar/StatusBarServiceKosmos.kt23
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/keyguard/TestScopeProvider.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/keyguard/TestScopeProvider.kt)0
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/DemoModeKosmos.kt25
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/InitControllerKosmos.kt21
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModelKosmos.kt29
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeCommandQueue.kt24
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt34
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt5
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAlternateBouncerTransitionViewModelKosmos.kt28
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OffToLockscreenTransitionViewModelKosmos.kt28
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/navigationbar/NavigationBarControllerKosmos.kt24
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/plugins/PluginDependencyKosmos.kt33
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt6
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/TileSquishinessRepositoryKosmos.kt21
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt5
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/TileSquishinessInteractorKosmos.kt23
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelKosmos.kt1
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileSquishinessViewModelKosmos.kt23
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt55
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeViewControllerKosmos.kt10
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/shade/transition/LinearLargeScreenShadeInterpolator.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/shade/transition/LinearLargeScreenShadeInterpolator.kt)0
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeOverlayContentViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/CommandQueueKosmos.kt8
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/NotificationEntryHelper.java (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryHelper.java)0
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/NotificationRemoteInputManagerKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/CommandQueueInitializerKosmos.kt39
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarInitializer.kt35
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarInitializerKosmos.kt28
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt52
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorKosmos.kt14
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/AutoHideKosmos.kt24
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/PhoneStatusBarKosmos.kt30
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileTelephonyHelpers.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileTelephonyHelpers.kt)0
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowController.kt54
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/StatusBarWindowControllerKosmos.kt23
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/data/repository/FakeStatusBarWindowStatePerDisplayRepository.kt46
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/data/repository/StatusBarWindowStateRepositoryStoreKosmos.kt5
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/stylus/FixedCapacityBatteryState.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/stylus/FixedCapacityBatteryState.kt)0
-rw-r--r--packages/VpnDialogs/res/values-kn/strings.xml2
-rw-r--r--ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java6
-rw-r--r--ravenwood/runtime-jni/ravenwood_sysprop.cpp2
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java237
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java4
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java86
-rw-r--r--services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java13
-rw-r--r--services/accessibility/java/com/android/server/accessibility/ProxyManager.java6
-rw-r--r--services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java93
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java333
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/RemoteServiceCallerImpl.java5
-rw-r--r--services/core/java/android/os/BatteryStatsInternal.java10
-rw-r--r--services/core/java/com/android/server/EventLogTags.logtags2
-rw-r--r--services/core/java/com/android/server/PackageWatchdog.java51
-rw-r--r--services/core/java/com/android/server/StorageManagerService.java4
-rw-r--r--services/core/java/com/android/server/accounts/AccountManagerService.java88
-rw-r--r--services/core/java/com/android/server/am/BatteryStatsService.java11
-rw-r--r--services/core/java/com/android/server/am/SettingsToPropertiesMapper.java9
-rw-r--r--services/core/java/com/android/server/am/flags.aconfig10
-rw-r--r--services/core/java/com/android/server/appop/AppOpsService.java3
-rw-r--r--services/core/java/com/android/server/biometrics/BiometricService.java8
-rw-r--r--services/core/java/com/android/server/biometrics/Utils.java17
-rw-r--r--services/core/java/com/android/server/crashrecovery/CrashRecoveryModule.java5
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java319
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController.java25
-rw-r--r--services/core/java/com/android/server/flags/pinner.aconfig13
-rw-r--r--services/core/java/com/android/server/inputmethod/HardwareKeyboardShortcutController.java3
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodDeviceConfigs.java1
-rw-r--r--services/core/java/com/android/server/inputmethod/ZeroJankProxy.java1
-rw-r--r--services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java7
-rw-r--r--services/core/java/com/android/server/notification/NotificationAttentionHelper.java98
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerService.java38
-rw-r--r--services/core/java/com/android/server/notification/NotificationRecord.java56
-rw-r--r--services/core/java/com/android/server/notification/PermissionHelper.java21
-rw-r--r--services/core/java/com/android/server/notification/PreferencesHelper.java11
-rw-r--r--services/core/java/com/android/server/notification/flags.aconfig17
-rw-r--r--services/core/java/com/android/server/pinner/PinRangeSource.java27
-rw-r--r--services/core/java/com/android/server/pinner/PinRangeSourceStatic.java37
-rw-r--r--services/core/java/com/android/server/pinner/PinRangeSourceStream.java43
-rw-r--r--services/core/java/com/android/server/pinner/PinnedFile.java61
-rw-r--r--services/core/java/com/android/server/pinner/PinnerService.java (renamed from services/core/java/com/android/server/PinnerService.java)572
-rw-r--r--services/core/java/com/android/server/pinner/PinnerUtils.java75
-rw-r--r--services/core/java/com/android/server/pm/DexOptHelper.java2
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerService.java33
-rw-r--r--services/core/java/com/android/server/power/stats/wakeups/CpuWakeupStats.java6
-rw-r--r--services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java15
-rw-r--r--services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java1
-rw-r--r--services/core/java/com/android/server/stats/pull/StatsPullAtomService.java4
-rw-r--r--services/core/java/com/android/server/vibrator/VibratorController.java62
-rw-r--r--services/core/java/com/android/server/vibrator/VibratorManagerService.java8
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperManagerService.java56
-rw-r--r--services/core/java/com/android/server/webkit/SystemImpl.java8
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java45
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecordInputSink.java35
-rw-r--r--services/core/java/com/android/server/wm/ActivityStarter.java4
-rw-r--r--services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java4
-rw-r--r--services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java26
-rw-r--r--services/core/java/com/android/server/wm/AppCompatCameraOverrides.java2
-rw-r--r--services/core/java/com/android/server/wm/AppCompatCameraPolicy.java60
-rw-r--r--services/core/java/com/android/server/wm/AppCompatConfiguration.java31
-rw-r--r--services/core/java/com/android/server/wm/AppCompatController.java9
-rw-r--r--services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java16
-rw-r--r--services/core/java/com/android/server/wm/BackgroundActivityStartController.java15
-rw-r--r--services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java37
-rw-r--r--services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java17
-rw-r--r--services/core/java/com/android/server/wm/DisplayPolicy.java5
-rw-r--r--services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java26
-rw-r--r--services/core/java/com/android/server/wm/InputMonitor.java3
-rw-r--r--services/core/java/com/android/server/wm/InsetsSourceProvider.java6
-rw-r--r--services/core/java/com/android/server/wm/ScreenRotationAnimation.java3
-rw-r--r--services/core/java/com/android/server/wm/Task.java14
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java4
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerShellCommand.java34
-rw-r--r--services/core/java/com/android/server/wm/WindowOrganizerController.java7
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java12
-rw-r--r--services/java/com/android/server/SystemServer.java1
-rw-r--r--services/profcollect/src/com/android/server/profcollect/Utils.java9
-rw-r--r--services/tests/RemoteProvisioningServiceTests/Android.bp1
-rw-r--r--services/tests/RemoteProvisioningServiceTests/src/com/android/server/security/rkp/RemoteProvisioningShellCommandTest.java1
-rw-r--r--services/tests/appfunctions/src/android/app/appfunctions/GenericDocumentWrapperTest.kt78
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayEventDeliveryTest.java188
-rw-r--r--services/tests/powerstatstests/res/xml/irq_device_map_3.xml3
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/wakeups/CpuWakeupStatsTest.java87
-rw-r--r--services/tests/servicestests/Android.bp2
-rw-r--r--services/tests/servicestests/src/com/android/server/PinnerServiceTest.java138
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java12
-rw-r--r--services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java62
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java56
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java130
-rw-r--r--services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java26
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java184
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java92
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java84
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java35
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerTest.java42
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java23
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java3
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java10
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppCompatCameraPolicyTest.java18
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java67
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java8
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java1
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java20
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java33
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java26
-rw-r--r--services/usb/java/com/android/server/usb/UsbDeviceManager.java16
-rw-r--r--telephony/java/android/telephony/TelephonyManager.java44
-rw-r--r--telephony/java/android/telephony/UiccAccessRule.java39
-rw-r--r--telephony/java/android/telephony/data/ApnSetting.java38
-rw-r--r--telephony/java/android/telephony/satellite/SatelliteManager.java9
-rw-r--r--telephony/java/com/android/internal/telephony/ITelephony.aidl24
-rw-r--r--tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml4
-rw-r--r--tests/FlickerTests/AppClose/AndroidTestTemplate.xml4
-rw-r--r--tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml4
-rw-r--r--tests/FlickerTests/FlickerService/AndroidTestTemplate.xml4
-rw-r--r--tests/FlickerTests/IME/AndroidTestTemplate.xml4
-rw-r--r--tests/FlickerTests/Notification/AndroidTestTemplate.xml4
-rw-r--r--tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml4
-rw-r--r--tests/FlickerTests/Rotation/AndroidTestTemplate.xml4
-rw-r--r--tests/broadcasts/unit/TEST_MAPPING2
-rw-r--r--tests/testables/Android.bp5
-rw-r--r--tests/testables/src/android/testing/TestWithLooperRule.java5
-rw-r--r--tests/testables/tests/Android.bp1
-rw-r--r--tests/testables/tests/src/android/testing/TestableLooperJUnit4Test.java42
821 files changed, 19646 insertions, 6491 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index 0f3b1c366fb0..033da2df9bf6 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -4944,10 +4944,14 @@ public class AlarmManagerService extends SystemService {
@Override
public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ if (action == null) {
+ return;
+ }
final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
synchronized (mLock) {
String pkgList[] = null;
- switch (intent.getAction()) {
+ switch (action) {
case Intent.ACTION_QUERY_PACKAGE_RESTART:
pkgList = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES);
for (String packageName : pkgList) {
diff --git a/core/api/current.txt b/core/api/current.txt
index 8eb881139b34..664dfe980b49 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -4919,6 +4919,7 @@ package android.app {
method public int getPendingIntentBackgroundActivityStartMode();
method public int getPendingIntentCreatorBackgroundActivityStartMode();
method public int getSplashScreenStyle();
+ method @FlaggedApi("com.android.window.flags.touch_pass_through_opt_in") public boolean isAllowPassThroughOnTouchOutside();
method @Deprecated public boolean isPendingIntentBackgroundActivityLaunchAllowed();
method public boolean isShareIdentityEnabled();
method public static android.app.ActivityOptions makeBasic();
@@ -4932,6 +4933,7 @@ package android.app {
method public static android.app.ActivityOptions makeTaskLaunchBehind();
method public static android.app.ActivityOptions makeThumbnailScaleUpAnimation(android.view.View, android.graphics.Bitmap, int, int);
method public void requestUsageTimeReport(android.app.PendingIntent);
+ method @FlaggedApi("com.android.window.flags.touch_pass_through_opt_in") public void setAllowPassThroughOnTouchOutside(boolean);
method public android.app.ActivityOptions setAppVerificationBundle(android.os.Bundle);
method public android.app.ActivityOptions setLaunchBounds(@Nullable android.graphics.Rect);
method public android.app.ActivityOptions setLaunchDisplayId(int);
@@ -6854,6 +6856,47 @@ package android.app {
method public android.app.Notification.MessagingStyle.Message setData(String, android.net.Uri);
}
+ @FlaggedApi("android.app.api_rich_ongoing") public static class Notification.ProgressStyle extends android.app.Notification.Style {
+ ctor public Notification.ProgressStyle();
+ method @NonNull public android.app.Notification.ProgressStyle addProgressSegment(@NonNull android.app.Notification.ProgressStyle.Segment);
+ method @NonNull public android.app.Notification.ProgressStyle addProgressStep(@NonNull android.app.Notification.ProgressStyle.Step);
+ method public int getProgress();
+ method @Nullable public android.graphics.drawable.Icon getProgressEndIcon();
+ method public int getProgressMax();
+ method @NonNull public java.util.List<android.app.Notification.ProgressStyle.Segment> getProgressSegments();
+ method @Nullable public android.graphics.drawable.Icon getProgressStartIcon();
+ method @NonNull public java.util.List<android.app.Notification.ProgressStyle.Step> getProgressSteps();
+ method @Nullable public android.graphics.drawable.Icon getProgressTrackerIcon();
+ method public boolean isProgressIndeterminate();
+ method public boolean isStyledByProgress();
+ method @NonNull public android.app.Notification.ProgressStyle setProgress(int);
+ method @NonNull public android.app.Notification.ProgressStyle setProgressEndIcon(@Nullable android.graphics.drawable.Icon);
+ method @NonNull public android.app.Notification.ProgressStyle setProgressIndeterminate(boolean);
+ method @NonNull public android.app.Notification.ProgressStyle setProgressSegments(@NonNull java.util.List<android.app.Notification.ProgressStyle.Segment>);
+ method @NonNull public android.app.Notification.ProgressStyle setProgressStartIcon(@Nullable android.graphics.drawable.Icon);
+ method @NonNull public android.app.Notification.ProgressStyle setProgressSteps(@NonNull java.util.List<android.app.Notification.ProgressStyle.Step>);
+ method @NonNull public android.app.Notification.ProgressStyle setProgressTrackerIcon(@Nullable android.graphics.drawable.Icon);
+ method @NonNull public android.app.Notification.ProgressStyle setStyledByProgress(boolean);
+ }
+
+ public static final class Notification.ProgressStyle.Segment {
+ ctor public Notification.ProgressStyle.Segment(int);
+ method @ColorInt public int getColor();
+ method public int getLength();
+ method public int getStableId();
+ method @NonNull public android.app.Notification.ProgressStyle.Segment setColor(@ColorInt int);
+ method @NonNull public android.app.Notification.ProgressStyle.Segment setStableId(int);
+ }
+
+ public static final class Notification.ProgressStyle.Step {
+ ctor public Notification.ProgressStyle.Step(int);
+ method @ColorInt public int getColor();
+ method public int getPosition();
+ method public int getStableId();
+ method @NonNull public android.app.Notification.ProgressStyle.Step setColor(@ColorInt int);
+ method @NonNull public android.app.Notification.ProgressStyle.Step setStableId(int);
+ }
+
public abstract static class Notification.Style {
ctor @Deprecated public Notification.Style();
method public android.app.Notification build();
@@ -8732,13 +8775,20 @@ package android.app.admin {
package android.app.appfunctions {
@FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public final class AppFunctionManager {
- method @RequiresPermission(anyOf={"android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED", "android.permission.EXECUTE_APP_FUNCTIONS"}, conditional=true) public void executeAppFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appfunctions.ExecuteAppFunctionResponse>);
+ method @Deprecated @RequiresPermission(anyOf={"android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED", "android.permission.EXECUTE_APP_FUNCTIONS"}, conditional=true) public void executeAppFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appfunctions.ExecuteAppFunctionResponse>);
+ method @RequiresPermission(anyOf={"android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED", "android.permission.EXECUTE_APP_FUNCTIONS"}, conditional=true) public void executeAppFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<android.app.appfunctions.ExecuteAppFunctionResponse>);
+ method public void isAppFunctionEnabled(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>);
+ method public void setAppFunctionEnabled(@NonNull String, int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,java.lang.Exception>);
+ field public static final int APP_FUNCTION_STATE_DEFAULT = 0; // 0x0
+ field public static final int APP_FUNCTION_STATE_DISABLED = 2; // 0x2
+ field public static final int APP_FUNCTION_STATE_ENABLED = 1; // 0x1
}
@FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public abstract class AppFunctionService extends android.app.Service {
ctor public AppFunctionService();
method @NonNull public final android.os.IBinder onBind(@Nullable android.content.Intent);
- method @MainThread public abstract void onExecuteFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.function.Consumer<android.app.appfunctions.ExecuteAppFunctionResponse>);
+ method @Deprecated @MainThread public abstract void onExecuteFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.function.Consumer<android.app.appfunctions.ExecuteAppFunctionResponse>);
+ method @MainThread public void onExecuteFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<android.app.appfunctions.ExecuteAppFunctionResponse>);
field @NonNull public static final String SERVICE_INTERFACE = "android.app.appfunctions.AppFunctionService";
}
@@ -8773,6 +8823,7 @@ package android.app.appfunctions {
field public static final String PROPERTY_RETURN_VALUE = "returnValue";
field public static final int RESULT_APP_UNKNOWN_ERROR = 2; // 0x2
field public static final int RESULT_DENIED = 1; // 0x1
+ field public static final int RESULT_DISABLED = 6; // 0x6
field public static final int RESULT_INTERNAL_ERROR = 3; // 0x3
field public static final int RESULT_INVALID_ARGUMENT = 4; // 0x4
field public static final int RESULT_OK = 0; // 0x0
@@ -46655,6 +46706,8 @@ package android.telephony.data {
field public static final int TYPE_IMS = 64; // 0x40
field public static final int TYPE_MCX = 1024; // 0x400
field public static final int TYPE_MMS = 2; // 0x2
+ field @FlaggedApi("com.android.internal.telephony.flags.oem_paid_private") public static final int TYPE_OEM_PAID = 65536; // 0x10000
+ field @FlaggedApi("com.android.internal.telephony.flags.oem_paid_private") public static final int TYPE_OEM_PRIVATE = 131072; // 0x20000
field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final int TYPE_RCS = 32768; // 0x8000
field public static final int TYPE_SUPL = 4; // 0x4
field public static final int TYPE_VSIM = 4096; // 0x1000
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index bfddf4fb5fac..20bcf5fdf6fb 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -442,6 +442,7 @@ package android {
public static final class R.attr {
field public static final int allowClearUserDataOnFailedRestore = 16844288; // 0x1010600
+ field @FlaggedApi("android.permission.flags.replace_body_sensor_permission_enabled") public static final int backgroundPermission;
field @FlaggedApi("android.content.res.manifest_flagging") public static final int featureFlag = 16844428; // 0x101068c
field public static final int gameSessionService = 16844373; // 0x1010655
field public static final int hotwordDetectionService = 16844326; // 0x1010626
@@ -6833,6 +6834,16 @@ package android.hardware.soundtrigger {
field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.SoundTrigger.RecognitionConfig> CREATOR;
}
+ public static final class SoundTrigger.RecognitionConfig.Builder {
+ ctor public SoundTrigger.RecognitionConfig.Builder();
+ method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig build();
+ method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig.Builder setAllowMultipleTriggers(boolean);
+ method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig.Builder setAudioCapabilities(int);
+ method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig.Builder setCaptureRequested(boolean);
+ method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig.Builder setData(@Nullable byte[]);
+ method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig.Builder setKeyphrases(@NonNull java.util.Collection<android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra>);
+ }
+
public static class SoundTrigger.RecognitionEvent {
method @Nullable public android.media.AudioFormat getCaptureFormat();
method public int getCaptureSession();
@@ -15878,6 +15889,8 @@ package android.telephony.data {
field public static final String TYPE_IMS_STRING = "ims";
field public static final String TYPE_MCX_STRING = "mcx";
field public static final String TYPE_MMS_STRING = "mms";
+ field @FlaggedApi("com.android.internal.telephony.flags.oem_paid_private") public static final String TYPE_OEM_PAID_STRING = "oem_paid";
+ field @FlaggedApi("com.android.internal.telephony.flags.oem_paid_private") public static final String TYPE_OEM_PRIVATE_STRING = "oem_private";
field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String TYPE_RCS_STRING = "rcs";
field public static final String TYPE_SUPL_STRING = "supl";
field public static final String TYPE_VSIM_STRING = "vsim";
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index cc9e8367dc3d..0a10920154b8 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1614,15 +1614,15 @@ package android.hardware.camera2 {
public final class CameraManager {
method @NonNull public android.hardware.camera2.CameraCharacteristics getCameraCharacteristics(@NonNull String, boolean) throws android.hardware.camera2.CameraAccessException;
method public String[] getCameraIdListNoLazy() throws android.hardware.camera2.CameraAccessException;
- method @FlaggedApi("com.android.window.flags.camera_compat_for_freeform") public static int getRotationOverrideInternal(@Nullable android.content.Context, @Nullable android.content.pm.PackageManager, @Nullable String);
+ method @FlaggedApi("com.android.window.flags.enable_camera_compat_for_desktop_windowing") public static int getRotationOverrideInternal(@Nullable android.content.Context, @Nullable android.content.pm.PackageManager, @Nullable String);
method @RequiresPermission(android.Manifest.permission.CAMERA) public void openCamera(@NonNull String, boolean, @Nullable android.os.Handler, @NonNull android.hardware.camera2.CameraDevice.StateCallback) throws android.hardware.camera2.CameraAccessException;
method @RequiresPermission(allOf={android.Manifest.permission.SYSTEM_CAMERA, android.Manifest.permission.CAMERA}) public void openCamera(@NonNull String, int, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.CameraDevice.StateCallback) throws android.hardware.camera2.CameraAccessException;
method public static boolean shouldOverrideToPortrait(@Nullable android.content.pm.PackageManager, @Nullable String);
field public static final String LANDSCAPE_TO_PORTRAIT_PROP = "camera.enable_landscape_to_portrait";
field public static final long OVERRIDE_CAMERA_LANDSCAPE_TO_PORTRAIT = 250678880L; // 0xef10e60L
- field @FlaggedApi("com.android.window.flags.camera_compat_for_freeform") public static final int ROTATION_OVERRIDE_NONE = 0; // 0x0
- field @FlaggedApi("com.android.window.flags.camera_compat_for_freeform") public static final int ROTATION_OVERRIDE_OVERRIDE_TO_PORTRAIT = 1; // 0x1
- field @FlaggedApi("com.android.window.flags.camera_compat_for_freeform") public static final int ROTATION_OVERRIDE_ROTATION_ONLY = 2; // 0x2
+ field @FlaggedApi("com.android.window.flags.enable_camera_compat_for_desktop_windowing") public static final int ROTATION_OVERRIDE_NONE = 0; // 0x0
+ field @FlaggedApi("com.android.window.flags.enable_camera_compat_for_desktop_windowing") public static final int ROTATION_OVERRIDE_OVERRIDE_TO_PORTRAIT = 1; // 0x1
+ field @FlaggedApi("com.android.window.flags.enable_camera_compat_for_desktop_windowing") public static final int ROTATION_OVERRIDE_ROTATION_ONLY = 2; // 0x2
}
public abstract static class CameraManager.AvailabilityCallback {
@@ -1887,7 +1887,7 @@ package android.hardware.soundtrigger {
}
@FlaggedApi("android.media.soundtrigger.manager_api") public static final class SoundTrigger.RecognitionConfig implements android.os.Parcelable {
- ctor public SoundTrigger.RecognitionConfig(boolean, boolean, @Nullable android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra[], @Nullable byte[], int);
+ ctor @Deprecated public SoundTrigger.RecognitionConfig(boolean, boolean, @Nullable android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra[], @Nullable byte[], int);
ctor public SoundTrigger.RecognitionConfig(boolean, boolean, @Nullable android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra[], @Nullable byte[]);
}
diff --git a/core/java/Android.bp b/core/java/Android.bp
index 92bca3cfbef2..99046328b1e2 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -21,14 +21,52 @@ filegroup {
"**/*.aidl",
":framework-nfc-non-updatable-sources",
":messagequeue-gen",
+ ":ranging_stack_mock_initializer",
],
// Exactly one MessageQueue.java will be added to srcs by messagequeue-gen
exclude_srcs: [
"android/os/*MessageQueue/**/*.java",
+ "android/ranging/**/*.java",
],
visibility: ["//frameworks/base"],
}
+//Mock to allow service registry for ranging stack.
+//TODO(b/331206299): Remove this after RELEASE_RANGING_STACK is ramped up to next.
+soong_config_module_type {
+ name: "ranging_stack_framework_mock_init",
+ module_type: "genrule",
+ config_namespace: "bootclasspath",
+ bool_variables: [
+ "release_ranging_stack",
+ ],
+ properties: [
+ "srcs",
+ "cmd",
+ "out",
+ ],
+}
+
+// The actual RangingFrameworkInitializer is present in packages/modules/Uwb/ranging/framework.
+// Mock RangingFrameworkInitializer does nothing and allows to successfully build
+// SystemServiceRegistry after registering for system service in SystemServiceRegistry both with
+// and without build flag RELEASE_RANGING_STACK enabled.
+ranging_stack_framework_mock_init {
+ name: "ranging_stack_mock_initializer",
+ soong_config_variables: {
+ release_ranging_stack: {
+ cmd: "touch $(out)",
+ // Adding an empty file as out is mandatory.
+ out: ["android/ranging/empty_ranging_fw.txt"],
+ conditions_default: {
+ srcs: ["android/ranging/mock/RangingFrameworkInitializer.java"],
+ cmd: "mkdir -p android/ranging/; cp $(in) $(out);",
+ out: ["android/ranging/RangingFrameworkInitializer.java"],
+ },
+ },
+ },
+}
+
// Add selected MessageQueue.java implementation to srcs
soong_config_module_type {
name: "release_package_messagequeue_implementation_srcs",
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 91aa225039a4..0d183c7c37aa 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -26,6 +26,7 @@ import static android.content.Intent.FLAG_RECEIVER_FOREGROUND;
import static android.view.Display.INVALID_DISPLAY;
import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -453,6 +454,10 @@ public class ActivityOptions extends ComponentOptions {
private static final String KEY_PENDING_INTENT_CREATOR_BACKGROUND_ACTIVITY_START_MODE =
"android.activity.pendingIntentCreatorBackgroundActivityStartMode";
+ /** See {@link #setAllowPassThroughOnTouchOutside(boolean)}. */
+ private static final String KEY_ALLOW_PASS_THROUGH_ON_TOUCH_OUTSIDE =
+ "android.activity.allowPassThroughOnTouchOutside";
+
/**
* @see #setLaunchCookie
* @hide
@@ -554,6 +559,7 @@ public class ActivityOptions extends ComponentOptions {
private int mPendingIntentCreatorBackgroundActivityStartMode =
MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
private boolean mDisableStartingWindow;
+ private boolean mAllowPassThroughOnTouchOutside;
/**
* Create an ActivityOptions specifying a custom animation to run when
@@ -1416,6 +1422,7 @@ public class ActivityOptions extends ComponentOptions {
KEY_PENDING_INTENT_CREATOR_BACKGROUND_ACTIVITY_START_MODE,
MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED);
mDisableStartingWindow = opts.getBoolean(KEY_DISABLE_STARTING_WINDOW);
+ mAllowPassThroughOnTouchOutside = opts.getBoolean(KEY_ALLOW_PASS_THROUGH_ON_TOUCH_OUTSIDE);
mAnimationAbortListener = IRemoteCallback.Stub.asInterface(
opts.getBinder(KEY_ANIM_ABORT_LISTENER));
}
@@ -1839,6 +1846,39 @@ public class ActivityOptions extends ComponentOptions {
&& mLaunchIntoPipParams.isLaunchIntoPip();
}
+ /**
+ * Returns whether the source activity allows the overlaying activities from the to-be-launched
+ * app to pass through touch events to it when touches fall outside the content window.
+ *
+ * @see #setAllowPassThroughOnTouchOutside(boolean)
+ */
+ @FlaggedApi(com.android.window.flags.Flags.FLAG_TOUCH_PASS_THROUGH_OPT_IN)
+ public boolean isAllowPassThroughOnTouchOutside() {
+ return mAllowPassThroughOnTouchOutside;
+ }
+
+ /**
+ * Sets whether the source activity allows the overlaying activities from the to-be-launched
+ * app to pass through touch events to it when touches fall outside the content window.
+ *
+ * <p> By default, touches that fall on a translucent non-touchable area of an overlaying
+ * activity window are blocked from passing through to the activity below (source activity),
+ * unless the overlaying activity is from the same UID as the source activity. The source
+ * activity may use this method to opt in and allow the overlaying activities from the
+ * to-be-launched app to pass through touches to itself. The source activity needs to ensure
+ * that it trusts the overlaying activity and its content is not vulnerable to UI redressing
+ * attacks. The flag is ignored if the context calling
+ * {@link Context#startActivity(Intent, Bundle)} is not an activity.
+ *
+ * <p> For backward compatibility, apps with target SDK 35 and below may still receive
+ * pass-through touches without opt-in if the cross-uid activity is launched by the source
+ * activity.
+ */
+ @FlaggedApi(com.android.window.flags.Flags.FLAG_TOUCH_PASS_THROUGH_OPT_IN)
+ public void setAllowPassThroughOnTouchOutside(boolean allowed) {
+ mAllowPassThroughOnTouchOutside = allowed;
+ }
+
/** @hide */
public int getLaunchActivityType() {
return mLaunchActivityType;
@@ -2520,6 +2560,10 @@ public class ActivityOptions extends ComponentOptions {
if (mDisableStartingWindow) {
b.putBoolean(KEY_DISABLE_STARTING_WINDOW, mDisableStartingWindow);
}
+ if (mAllowPassThroughOnTouchOutside) {
+ b.putBoolean(KEY_ALLOW_PASS_THROUGH_ON_TOUCH_OUTSIDE,
+ mAllowPassThroughOnTouchOutside);
+ }
b.putBinder(KEY_ANIM_ABORT_LISTENER,
mAnimationAbortListener != null ? mAnimationAbortListener.asBinder() : null);
return b;
diff --git a/core/java/android/app/DownloadManager.java b/core/java/android/app/DownloadManager.java
index b781ce50c4db..f21c3e8d44d6 100644
--- a/core/java/android/app/DownloadManager.java
+++ b/core/java/android/app/DownloadManager.java
@@ -493,6 +493,9 @@ public class DownloadManager {
* {@link Environment#getExternalStoragePublicDirectory(String)} with
* {@link Environment#DIRECTORY_DOWNLOADS}).
*
+ * All non-visible downloads that are not modified in the last 7 days will be deleted during
+ * idle runs.
+ *
* @param uri a file {@link Uri} indicating the destination for the downloaded file.
* @return this object
*/
@@ -796,7 +799,9 @@ public class DownloadManager {
* public Downloads directory (as returned by
* {@link Environment#getExternalStoragePublicDirectory(String)} with
* {@link Environment#DIRECTORY_DOWNLOADS}) will be visible in system's Downloads UI
- * and the rest will not be visible.
+ * and the rest will not be visible. All non-visible downloads that are not modified
+ * in the last 7 days will be deleted during idle runs.
+ *
* (e.g. {@link Context#getExternalFilesDir(String)}) will not be visible.
*/
@Deprecated
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 392a1f113c23..e8b0a36ffcfc 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -121,7 +121,6 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
@@ -783,10 +782,32 @@ public class Notification implements Parcelable
@FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
public static final int FLAG_PROMOTED_ONGOING = 0x00040000;
- private static final List<Class<? extends Style>> PLATFORM_STYLE_CLASSES = Arrays.asList(
- BigTextStyle.class, BigPictureStyle.class, InboxStyle.class, MediaStyle.class,
- DecoratedCustomViewStyle.class, DecoratedMediaCustomViewStyle.class,
- MessagingStyle.class, CallStyle.class);
+ private static final Set<Class<? extends Style>> PLATFORM_STYLE_CLASSES = Set.of(
+ BigTextStyle.class,
+ BigPictureStyle.class,
+ InboxStyle.class,
+ MediaStyle.class,
+ DecoratedCustomViewStyle.class,
+ DecoratedMediaCustomViewStyle.class,
+ MessagingStyle.class,
+ CallStyle.class
+ );
+
+ private static boolean isPlatformStyle(Style style) {
+ if (style == null) {
+ return false;
+ }
+
+ if (PLATFORM_STYLE_CLASSES.contains(style.getClass())) {
+ return true;
+ }
+
+ if (Flags.apiRichOngoing()) {
+ return style.getClass() == ProgressStyle.class;
+ }
+
+ return false;
+ }
/** @hide */
@IntDef(flag = true, prefix = {"FLAG_"}, value = {
@@ -1598,26 +1619,70 @@ public class Notification implements Parcelable
public static final String EXTRA_DECLINE_COLOR = "android.declineColor";
/**
- * {@link #extras} key: {@link Icon} of an image used as an overlay Icon on
- * {@link Notification#mLargeIcon} for {@link EnRouteStyle} notifications.
- * This extra is an {@code Icon}.
+ * {@link #extras} key: whether the notification should be colorized as
+ * supplied to {@link Builder#setColorized(boolean)}.
+ */
+ public static final String EXTRA_COLORIZED = "android.colorized";
+
+ /**
+ * {@link #extras} key: an arraylist of {@link android.app.Notification.ProgressStyle.Segment}
+ * bundles provided by a
+ * {@link android.app.Notification.ProgressStyle} notification as supplied to
+ * {@link ProgressStyle#setProgressSegments}
+ * or {@link ProgressStyle#addProgressSegment(ProgressStyle.Segment)}.
+ * This extra is a parcelable array list of bundles.
* @hide
*/
@FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
- public static final String EXTRA_ENROUTE_OVERLAY_ICON = "android.enrouteOverlayIcon";
+ public static final String EXTRA_PROGRESS_SEGMENTS = "android.progressSegments";
/**
- * {@link #extras} key: text used as a sub-text for the largeIcon of
- * {@link EnRouteStyle} notification. This extra is a {@code CharSequence}.
+ * {@link #extras} key: an arraylist of {@link android.app.Notification.ProgressStyle.Step}
+ * bundles provided by a
+ * {@link android.app.Notification.ProgressStyle} notification as supplied to
+ * {@link ProgressStyle#setProgressSteps}
+ * or {@link ProgressStyle#addProgressStep(ProgressStyle.Step)}.
+ * This extra is a parcelable array list of bundles.
* @hide
*/
@FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
- public static final String EXTRA_ENROUTE_LARGE_ICON_SUBTEXT = "android.enrouteLargeIconSubText";
+ public static final String EXTRA_PROGRESS_STEPS = "android.progressSteps";
+
/**
- * {@link #extras} key: whether the notification should be colorized as
- * supplied to {@link Builder#setColorized(boolean)}.
+ * {@link #extras} key: whether the progress bar should be styled by its progress as
+ * supplied to {@link ProgressStyle#setStyledByProgress}.
+ * This extra is a boolean.
+ * @hide
*/
- public static final String EXTRA_COLORIZED = "android.colorized";
+ @FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
+ public static final String EXTRA_STYLED_BY_PROGRESS = "android.styledByProgress";
+
+ /**
+ * {@link #extras} key: this is an {@link Icon} of an image to be
+ * shown as progress bar progress tracker icon in {@link ProgressStyle}, supplied to
+ *{@link ProgressStyle#setProgressTrackerIcon(Icon)}.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
+ public static final String EXTRA_PROGRESS_TRACKER_ICON = "android.progressTrackerIcon";
+
+ /**
+ * {@link #extras} key: this is an {@link Icon} of an image to be
+ * shown at the beginning of the progress bar in {@link ProgressStyle}, supplied to
+ *{@link ProgressStyle#setProgressStartIcon(Icon)}.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
+ public static final String EXTRA_PROGRESS_START_ICON = "android.progressStartIcon";
+
+ /**
+ * {@link #extras} key: this is an {@link Icon} of an image to be
+ * shown at the end of the progress bar in {@link ProgressStyle}, supplied to
+ *{@link ProgressStyle#setProgressEndIcon(Icon)}.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
+ public static final String EXTRA_PROGRESS_END_ICON = "android.progressEndIcon";
/**
* @hide
@@ -3071,7 +3136,9 @@ public class Notification implements Parcelable
}
if (Flags.apiRichOngoing()) {
- visitIconUri(visitor, extras.getParcelable(EXTRA_ENROUTE_OVERLAY_ICON, Icon.class));
+ visitIconUri(visitor, extras.getParcelable(EXTRA_PROGRESS_TRACKER_ICON, Icon.class));
+ visitIconUri(visitor, extras.getParcelable(EXTRA_PROGRESS_START_ICON, Icon.class));
+ visitIconUri(visitor, extras.getParcelable(EXTRA_PROGRESS_END_ICON, Icon.class));
}
if (mBubbleMetadata != null) {
@@ -3149,11 +3216,13 @@ public class Notification implements Parcelable
*/
@FlaggedApi(Flags.FLAG_UI_RICH_ONGOING)
public boolean hasPromotableStyle() {
- //TODO(b/367739672): Add progress style
- return extras == null || !extras.containsKey(Notification.EXTRA_TEMPLATE)
- || isStyle(Notification.BigPictureStyle.class)
- || isStyle(Notification.BigTextStyle.class)
- || isStyle(Notification.CallStyle.class);
+ final Class<? extends Style> notificationStyle = getNotificationStyle();
+
+ return notificationStyle == null
+ || BigPictureStyle.class.equals(notificationStyle)
+ || BigTextStyle.class.equals(notificationStyle)
+ || CallStyle.class.equals(notificationStyle)
+ || ProgressStyle.class.equals(notificationStyle);
}
/**
@@ -6630,7 +6699,7 @@ public class Notification implements Parcelable
// Custom views which come from a platform style class are safe, and thus do not need to
// be wrapped. Any subclass of those styles has the opportunity to make arbitrary
// changes to the RemoteViews, and thus can't be trusted as a fully vetted view.
- if (fromStyle && PLATFORM_STYLE_CLASSES.contains(mStyle.getClass())) {
+ if (fromStyle && isPlatformStyle(mStyle)) {
return false;
}
return mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S;
@@ -7971,6 +8040,12 @@ public class Notification implements Parcelable
return innerClass;
}
}
+
+ if (Flags.apiRichOngoing()) {
+ if (templateClass.equals(ProgressStyle.class.getName())) {
+ return ProgressStyle.class;
+ }
+ }
return null;
}
@@ -11083,92 +11158,396 @@ public class Notification implements Parcelable
}
/**
- * TODO(b/360827871): Make EnRouteStyle public.
- * A style used to represent the progress of a real-world journey with a known destination.
- * For example:
- * <ul>
- * <li>Delivery tracking</li>
- * <li>Ride progress</li>
- * <li>Flight tracking</li>
- * </ul>
+ * A Notification Style used to to define a notification whose expanded state includes
+ * a highly customizable progress bar with segments, steps, a custom tracker icon,
+ * and custom icons at the start and end of the progress bar.
+ *
+ * This style is suggested for use cases where the app is showing a tracker to the
+ * user of a thing they are interested in: the location of a car on its way
+ * to pick them up, food being delivered, or their own progress in a navigation
+ * journey.
+ *
+ * To use this style with your Notification, feed it to
+ * {@link Notification.Builder#setStyle(android.app.Notification.Style)} like so:
+ * <pre class="prettyprint">
+ * new Notification.Builder(context)
+ * .setSmallIcon(R.drawable.ic_notification)
+ * .setColor(Color.GREEN)
+ * .setColorized(true)
+ * .setContentTitle("Arrive 10:08 AM").
+ * .setContentText("Dominique Ansel Bakery Soho")
+ * .addAction(new Notification.Action("Exit navigation",...))
+ * .setStyle(new Notification.ProgressStyle()
+ * .setStyledByProgress(false)
+ * .setProgress(456)
+ * .setProgressTrackerIcon(Icon.createWithResource(R.drawable.ic_driving_tracker))
+ * .addProgressSegment(new Segment(41).setColor(Color.BLACK))
+ * .addProgressSegment(new Segment(552).setColor(Color.YELLOW))
+ * .addProgressSegment(new Segment(253).setColor(Color.YELLOW))
+ * .addProgressSegment(new Segment(94).setColor(Color.BLUE))
+ * .addProgressStep(new Step(60).setColor(Color.RED))
+ * .addProgressStep(new Step(560).setColor(Color.YELLOW))
+ * )
+ * </pre>
+ *
+ *
+ *
+ * NOTE: The progress bar layout will be mirrored for RTL layout.
+ * NOTE: The extras set by {@link Notification.Builder#setProgress} will be overridden by
+ * the values set on this style object when the notification is built.
*
- * The exact fields from {@link Notification} that are shown with this style may vary by
- * the surface where this update appears, but the following fields are recommended:
- * <ul>
- * <li>{@link Notification.Builder#setContentTitle}</li>
- * <li>{@link Notification.Builder#setContentText}</li>
- * <li>{@link Notification.Builder#setSubText}</li>
- * <li>{@link Notification.Builder#setLargeIcon}</li>
- * <li>{@link Notification.Builder#setProgress}</li>
- * <li>{@link Notification.Builder#setWhen} - This should be the future time of the next,
- * final, or most important stop on this journey.</li>
- * </ul>
- * @hide
*/
@FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
- public static class EnRouteStyle extends Notification.Style {
+ public static class ProgressStyle extends Notification.Style {
+ private static final String KEY_ELEMENT_STABLE_ID = "stableId";
+ private static final String KEY_ELEMENT_COLOR = "colorInt";
+ private static final String KEY_SEGMENT_LENGTH = "length";
+ private static final String KEY_STEP_POSITION = "position";
- @Nullable
- private Icon mOverlayIcon = null;
+ private static final int MAX_PROGRESS_SEGMENT_LIMIT = 15;
+ private static final int MAX_PROGRESS_STEP_LIMIT = 5;
+ private static final int DEFAULT_PROGRESS_MAX = 100;
+
+ private List<Segment> mProgressSegments = new ArrayList<>();
+ private List<Step> mProgressSteps = new ArrayList<>();
+
+ private int mProgress = 0;
+
+ private boolean mIndeterminate;
+
+ private boolean mIsStyledByProgress = true;
@Nullable
- private CharSequence mLargeIconSubText = null;
+ private Icon mTrackerIcon;
+ @Nullable
+ private Icon mStartIcon;
+ @Nullable
+ private Icon mEndIcon;
+
+ /**
+ * @hide
+ */
+ @Override
+ public boolean areNotificationsVisiblyDifferent(Style other) {
+ if (other == null || getClass() != other.getClass()) {
+ return true;
+ }
+
+ final ProgressStyle progressStyle = (ProgressStyle) other;
- public EnRouteStyle() {
+ /**
+ * @see #setProgressIndeterminate
+ */
+ if (!Objects.equals(mIndeterminate, progressStyle.mIndeterminate)) {
+ return true;
+ }
+ boolean nonIndeterminateCheckResult = false;
+ if (!mIndeterminate) {
+ nonIndeterminateCheckResult = !Objects.equals(mProgress, progressStyle.mProgress)
+ || !Objects.equals(mIsStyledByProgress, progressStyle.mIsStyledByProgress)
+ || !Objects.equals(mProgressSegments, progressStyle.mProgressSegments)
+ || !Objects.equals(mProgressSteps, progressStyle.mProgressSteps)
+ || !Objects.equals(mTrackerIcon, progressStyle.mTrackerIcon);
+ }
+
+ return !Objects.equals(mStartIcon, progressStyle.mStartIcon)
+ || !Objects.equals(mEndIcon, progressStyle.mEndIcon)
+ || nonIndeterminateCheckResult;
}
/**
- * Returns the overlay icon to be displayed on {@link Notification#mLargeIcon}.
- * @see EnRouteStyle#setOverlayIcon
+ * Gets the segments that define the background layer of the progress bar.
+ *
+ * If no segments are provided, the progress bar will be rendered with a single segment
+ * with length 100 and default color.
+ *
+ * @see #setProgressSegments
+ * @see #addProgressSegment
+ * @see Segment
*/
- @Nullable
- public Icon getOverlayIcon() {
- return mOverlayIcon;
+ public @NonNull List<Segment> getProgressSegments() {
+ return mProgressSegments;
}
/**
- * Optional icon to be displayed on {@link Notification#mLargeIcon}.
+ * Sets or replaces the segments of the progress bar.
*
- * This image will be cropped to a circle and will obscure
- * a semicircle of the right side of the large icon.
+ * Segments allow for creating progress bars with multiple colors or sections
+ * to represent different stages or categories of progress.
+ * For example, Traffic conditions along a navigation journey.
+ * @see Segment
*/
- @NonNull
- public EnRouteStyle setOverlayIcon(@Nullable Icon overlayIcon) {
- mOverlayIcon = overlayIcon;
+ public @NonNull ProgressStyle setProgressSegments(@NonNull List<Segment> progressSegments) {
+ mProgressSegments = new ArrayList<>(progressSegments.size());
return this;
}
/**
- * Returns the sub-text for {@link Notification#mLargeIcon}.
- * @see EnRouteStyle#setLargeIconSubText
+ * Appends a segment to the end of the progress bar.
+ *
+ * Segments allow for creating progress bars with multiple colors or sections
+ * to represent different stages or categories of progress.
+ * For example, Traffic conditions along a navigation journey.
+ * @see Segment
*/
- @Nullable
- public CharSequence getLargeIconSubText() {
- return mLargeIconSubText;
+ public @NonNull ProgressStyle addProgressSegment(@NonNull Segment segment) {
+ if (mProgressSegments == null) {
+ mProgressSegments = new ArrayList<>();
+ }
+ mProgressSegments.add(segment);
+
+ return this;
}
/**
- * Optional text which generally related to
- * the {@link Notification.Builder#setLargeIcon} or {@link #setOverlayIcon} or both.
+ * Gets the steps that are displayed on the progress bar.
+ *.
+ * @see #setProgressSteps
+ * @see #addProgressStep
+ * @see Step
*/
- @NonNull
- public EnRouteStyle setLargeIconSubText(@Nullable CharSequence largeIconSubText) {
- mLargeIconSubText = stripStyling(largeIconSubText);
+ public @NonNull List<Step> getProgressSteps() {
+ return mProgressSteps;
+ }
+
+ /**
+ * Replaces all the progress steps.
+ *
+ * Steps are designated points within a progressbar to visualize
+ * distinct stages or milestones.
+ * For example, you might use steps to mark stops in a multi-stop
+ * navigation journey, where each step represents a destination.
+ * @see Step
+ */
+ public @NonNull ProgressStyle setProgressSteps(@NonNull List<Step> steps) {
+ mProgressSteps = new ArrayList<>(steps);
+ return this;
+ }
+
+ /**
+ * Adds another step.
+ *
+ * Steps are designated points within a progressbar to visualize
+ * distinct stages or milestones.
+ * For example, you might use steps to mark stops in a multi-stop
+ * navigation journey, where each step represents a destination.
+ *
+ * Steps can be added in any order, as their
+ * position within the progress bar is determined by their individual
+ * {@link Step#getPosition()}.
+ * @see Step
+ */
+ public @NonNull ProgressStyle addProgressStep(@NonNull Step step) {
+ if (mProgressSteps == null) {
+ mProgressSteps = new ArrayList<>();
+ }
+ mProgressSteps.add(step);
+
+ return this;
+ }
+
+ /**
+ * Gets the progress value of the progress bar.
+ * @see #setProgress
+ */
+ public int getProgress() {
+ return mProgress;
+ }
+
+ /**
+ * Specifies the progress (in the same units as {@link Segment#getLength()})
+ * of the tracker along the length of the bar.
+ *
+ * The max progress value is the sum of all Segment lengths.
+ * The default value is 0.
+ */
+ public @NonNull ProgressStyle setProgress(int progress) {
+ mProgress = progress;
+ return this;
+ }
+
+ /**
+ * Gets the sum of the lengths of all Segments in the style, which
+ * defines the maximum progress. Defaults to 100 when segments are omitted.
+ */
+ public int getProgressMax() {
+ final List<Segment> progressSegment = mProgressSegments;
+ if (progressSegment == null || progressSegment.isEmpty()) {
+ return DEFAULT_PROGRESS_MAX;
+ } else {
+ int progressMax = 0;
+ int validSegmentCount = 0;
+ for (int i = 0; i < progressSegment.size()
+ && validSegmentCount < MAX_PROGRESS_SEGMENT_LIMIT; i++) {
+ int segmentLength = progressSegment.get(i).getLength();
+ if (segmentLength > 0) {
+ try {
+ progressMax = Math.addExact(progressMax, segmentLength);
+ validSegmentCount++;
+ } catch (ArithmeticException e) {
+ Log.e(TAG,
+ "Notification.ProgressStyle segment total overflowed.", e);
+ return DEFAULT_PROGRESS_MAX;
+ }
+ }
+ }
+
+ if (validSegmentCount == 0) {
+ return DEFAULT_PROGRESS_MAX;
+ }
+
+ return progressMax;
+ }
+
+ }
+
+ /**
+ * Get indeterminate value of the progress bar.
+ * @see #setProgressIndeterminate
+ */
+ public boolean isProgressIndeterminate() {
+ return mIndeterminate;
+ }
+
+ /**
+ * Used to indicate an initialization state without a known progress amount.
+ * When specified, the following fields are ignored:
+ * @see #setProgress
+ * @see #setProgressSegments
+ * @see #setProgressSteps
+ * @see #setProgressTrackerIcon
+ * @see #setStyledByProgress
+ *
+ * If the app provides exactly one Segment, that segment's color will be
+ * used to style the indeterminate bar.
+ */
+ public @NonNull ProgressStyle setProgressIndeterminate(boolean indeterminate) {
+ mIndeterminate = indeterminate;
+ return this;
+ }
+
+ /**
+ * Gets whether the progress bar's style is based on its progress.
+ * @see #setStyledByProgress
+ */
+ public boolean isStyledByProgress() {
+ return mIsStyledByProgress;
+ }
+
+ /**
+ * Indicates whether the segments and steps will be styled differently
+ * based on whether they are behind or ahead of the current progress.
+ * When true, segments appearing ahead of the current progress will be given a
+ * slightly different appearance to indicate that it is part of the progress bar
+ * that is not "filled".
+ * When false, all segments will be given the filled appearance, and it will be
+ * the app's responsibility to use #setProgressTrackerIcon or segment colors
+ * to make the current progress clear to the user.
+ * the default value is true.
+ */
+ public @NonNull ProgressStyle setStyledByProgress(boolean enabled) {
+ mIsStyledByProgress = enabled;
+ return this;
+ }
+
+
+ /**
+ * Gets the progress tracker icon for the progress bar.
+ * @see #setProgressTrackerIcon
+ */
+ public @Nullable Icon getProgressTrackerIcon() {
+ return mTrackerIcon;
+ }
+
+ /**
+ * An optional icon that can appear as an overlay on the bar at the point of
+ * current progress.
+ * Aspect ratio may be anywhere from 2:1 to 1:2; content outside that
+ * aspect ratio range will be cropped.
+ * This icon will be mirrored in RTL.
+ */
+ public @NonNull ProgressStyle setProgressTrackerIcon(@Nullable Icon trackerIcon) {
+ mTrackerIcon = trackerIcon;
+ return this;
+ }
+
+ /**
+ * Gets the progress bar start icon.
+ * @see #setProgressStartIcon
+ */
+ public @Nullable Icon getProgressStartIcon() {
+ return mStartIcon;
+ }
+
+ /**
+ * An optional square icon that appears at the start of the progress bar.
+ * This icon will be cropped to its central square.
+ * This icon will NOT be mirrored in RTL layouts.
+ */
+ public @NonNull ProgressStyle setProgressStartIcon(@Nullable Icon startIcon) {
+ mStartIcon = startIcon;
return this;
}
- /**
+ /**
+ * Gets the progress bar end icon.
+ * @see #setProgressEndIcon(Icon)
+ */
+ public @Nullable Icon getProgressEndIcon() {
+ return mEndIcon;
+ }
+
+ /**
+ * An optional square icon that appears at the end of the progress bar.
+ * This icon will be cropped to its central square.
+ * This icon will NOT be mirrored in RTL layouts.
+ */
+ public @NonNull ProgressStyle setProgressEndIcon(@Nullable Icon endIcon) {
+ mEndIcon = endIcon;
+ return this;
+ }
+
+ /**
* @hide
*/
@Override
- public boolean areNotificationsVisiblyDifferent(Style other) {
- if (other == null || getClass() != other.getClass()) {
- return true;
+ public void purgeResources() {
+ super.purgeResources();
+ if (mTrackerIcon != null) {
+ mTrackerIcon.convertToAshmem();
}
+ if (mStartIcon != null) {
+ mStartIcon.convertToAshmem();
+ }
+ if (mEndIcon != null) {
+ mEndIcon.convertToAshmem();
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void reduceImageSizes(Context context) {
+ super.reduceImageSizes(context);
- final EnRouteStyle enRouteStyle = (EnRouteStyle) other;
- return !Objects.equals(mOverlayIcon, enRouteStyle.mOverlayIcon)
- || !Objects.equals(mLargeIconSubText, enRouteStyle.mLargeIconSubText);
+ final Resources resources = context.getResources();
+
+ int progressIconSize =
+ resources.getDimensionPixelSize(R.dimen.notification_progress_icon_size);
+ if (mStartIcon != null) {
+ mStartIcon.scaleDownIfNecessary(progressIconSize, progressIconSize);
+ }
+ if (mEndIcon != null) {
+ mEndIcon.scaleDownIfNecessary(progressIconSize, progressIconSize);
+ }
+ if (mTrackerIcon != null) {
+ int progressTrackerWidth = resources.getDimensionPixelSize(
+ R.dimen.notification_progress_tracker_width);
+ int progressTrackerHeight = resources.getDimensionPixelSize(
+ R.dimen.notification_progress_tracker_height);
+ mTrackerIcon.scaleDownIfNecessary(progressTrackerWidth, progressTrackerHeight);
+ }
}
/**
@@ -11177,8 +11556,33 @@ public class Notification implements Parcelable
@Override
public void addExtras(Bundle extras) {
super.addExtras(extras);
- extras.putParcelable(EXTRA_ENROUTE_OVERLAY_ICON, mOverlayIcon);
- extras.putCharSequence(EXTRA_ENROUTE_LARGE_ICON_SUBTEXT, mLargeIconSubText);
+ extras.putParcelableArrayList(EXTRA_PROGRESS_SEGMENTS,
+ getProgressSegmentsAsBundleList(mProgressSegments));
+ extras.putParcelableArrayList(EXTRA_PROGRESS_STEPS,
+ getProgressStepsAsBundleList(mProgressSteps));
+
+ extras.putInt(EXTRA_PROGRESS, mProgress);
+ extras.putBoolean(EXTRA_PROGRESS_INDETERMINATE, mIndeterminate);
+ extras.putInt(EXTRA_PROGRESS_MAX, getProgressMax());
+ extras.putBoolean(EXTRA_STYLED_BY_PROGRESS, mIsStyledByProgress);
+
+ if (mTrackerIcon != null) {
+ extras.putParcelable(EXTRA_PROGRESS_TRACKER_ICON, mTrackerIcon);
+ } else {
+ extras.remove(EXTRA_PROGRESS_TRACKER_ICON);
+ }
+
+ if (mStartIcon != null) {
+ extras.putParcelable(EXTRA_PROGRESS_START_ICON, mStartIcon);
+ } else {
+ extras.remove(EXTRA_PROGRESS_START_ICON);
+ }
+
+ if (mEndIcon != null) {
+ extras.putParcelable(EXTRA_PROGRESS_END_ICON, mEndIcon);
+ } else {
+ extras.remove(EXTRA_PROGRESS_END_ICON);
+ }
}
/**
@@ -11187,35 +11591,285 @@ public class Notification implements Parcelable
@Override
protected void restoreFromExtras(Bundle extras) {
super.restoreFromExtras(extras);
- mOverlayIcon = extras.getParcelable(EXTRA_ENROUTE_OVERLAY_ICON, Icon.class);
- mLargeIconSubText = extras.getCharSequence(EXTRA_ENROUTE_LARGE_ICON_SUBTEXT);
+ mProgressSegments = getProgressSegmentsFromBundleList(
+ extras.getParcelableArrayList(EXTRA_PROGRESS_SEGMENTS, Bundle.class));
+ mProgress = extras.getInt(EXTRA_PROGRESS, 0);
+ mIndeterminate = extras.getBoolean(EXTRA_PROGRESS_INDETERMINATE, false);
+ mIsStyledByProgress = extras.getBoolean(EXTRA_STYLED_BY_PROGRESS, true);
+ mTrackerIcon = extras.getParcelable(EXTRA_PROGRESS_TRACKER_ICON, Icon.class);
+ mStartIcon = extras.getParcelable(EXTRA_PROGRESS_START_ICON, Icon.class);
+ mEndIcon = extras.getParcelable(EXTRA_PROGRESS_END_ICON, Icon.class);
+ mProgressSteps = getProgressStepsFromBundleList(
+ extras.getParcelableArrayList(EXTRA_PROGRESS_STEPS, Bundle.class));
}
/**
* @hide
*/
@Override
- public void purgeResources() {
- super.purgeResources();
- if (mOverlayIcon != null) {
- mOverlayIcon.convertToAshmem();
+ public boolean displayCustomViewInline() {
+ // This is a lie; True is returned for progress notifications to make sure
+ // that the custom view is not used instead of the template, but it will not
+ // actually be included.
+ return true;
+ }
+
+ private static @NonNull ArrayList<Bundle> getProgressSegmentsAsBundleList(
+ @Nullable List<Segment> progressSegments) {
+ final ArrayList<Bundle> segments = new ArrayList<>();
+ if (progressSegments != null && !progressSegments.isEmpty()) {
+ for (int i = 0; i < progressSegments.size(); i++) {
+ final Segment segment = progressSegments.get(i);
+ if (segment.getLength() <= 0) {
+ continue;
+ }
+
+ final Bundle bundle = new Bundle();
+ bundle.putInt(KEY_SEGMENT_LENGTH, segment.getLength());
+ bundle.putInt(KEY_ELEMENT_STABLE_ID, segment.getStableId());
+ bundle.putInt(KEY_ELEMENT_COLOR, segment.getColor());
+
+ segments.add(bundle);
+ }
}
+
+ return segments;
+ }
+
+ private static @NonNull List<Segment> getProgressSegmentsFromBundleList(
+ @Nullable List<Bundle> segmentBundleList) {
+ final ArrayList<Segment> segments = new ArrayList<>();
+ if (segmentBundleList != null && !segmentBundleList.isEmpty()) {
+ for (int i = 0; i < segmentBundleList.size(); i++) {
+ final Bundle segmentBundle = segmentBundleList.get(i);
+ final int length = segmentBundle.getInt(KEY_SEGMENT_LENGTH);
+ if (length <= 0) {
+ continue;
+ }
+
+ final int stableId = segmentBundle.getInt(KEY_ELEMENT_STABLE_ID);
+ final int color = segmentBundle.getInt(KEY_ELEMENT_COLOR,
+ Notification.COLOR_DEFAULT);
+ final Segment segment = new Segment(length)
+ .setStableId(stableId).setColor(color);
+
+ segments.add(segment);
+ }
+ }
+
+ return segments;
+ }
+
+ private static @NonNull ArrayList<Bundle> getProgressStepsAsBundleList(
+ @Nullable List<Step> progressSteps) {
+ final ArrayList<Bundle> steps = new ArrayList<>();
+ if (progressSteps != null && !progressSteps.isEmpty()) {
+ for (int i = 0; i < progressSteps.size(); i++) {
+ final Step step = progressSteps.get(i);
+ if (step.getPosition() < 0) {
+ continue;
+ }
+
+ final Bundle bundle = new Bundle();
+ bundle.putInt(KEY_STEP_POSITION, step.getPosition());
+ bundle.putInt(KEY_ELEMENT_STABLE_ID, step.getStableId());
+ bundle.putInt(KEY_ELEMENT_COLOR, step.getColor());
+
+ steps.add(bundle);
+ }
+ }
+
+ return steps;
+ }
+
+ private static @NonNull List<Step> getProgressStepsFromBundleList(
+ @Nullable List<Bundle> stepBundleList) {
+ final ArrayList<Step> steps = new ArrayList<>();
+
+ if (stepBundleList != null && !stepBundleList.isEmpty()) {
+ for (int i = 0; i < stepBundleList.size(); i++) {
+ final Bundle segmentBundle = stepBundleList.get(i);
+ final int position = segmentBundle.getInt(KEY_STEP_POSITION);
+ if (position < 0) {
+ continue;
+ }
+ final int stableId = segmentBundle.getInt(KEY_ELEMENT_STABLE_ID);
+ final int color = segmentBundle.getInt(KEY_ELEMENT_COLOR,
+ Notification.COLOR_DEFAULT);
+ final Step step = new Step(position).setStableId(stableId).setColor(color);
+ steps.add(step);
+ }
+ }
+
+ return steps;
}
/**
- * @hide
+ * A segment of the progress bar, which defines its length and color.
+ * Segments allow for creating progress bars with multiple colors or sections
+ * to represent different stages or categories of progress.
+ * For example, Traffic conditions along a navigation journey.
*/
- @Override
- public void reduceImageSizes(Context context) {
- super.reduceImageSizes(context);
- if (mOverlayIcon != null) {
- final Resources resources = context.getResources();
- final boolean isLowRam = ActivityManager.isLowRamDeviceStatic();
+ public static final class Segment {
+ private int mLength;
+ private int mStableId = 0;
+ @ColorInt
+ private int mColor = Notification.COLOR_DEFAULT;
- int rightIconSize = resources.getDimensionPixelSize(isLowRam
- ? R.dimen.notification_right_icon_size_low_ram
- : R.dimen.notification_right_icon_size);
- mOverlayIcon.scaleDownIfNecessary(rightIconSize, rightIconSize);
+ /**
+ * Create a segment with a non-zero length.
+ * @param length
+ * See {@link #getLength}
+ */
+ public Segment(int length) {
+ mLength = length;
+ }
+
+ /**
+ * The length of this Segment within the progress bar.
+ * This value has no units, it is just relative to the length of other segments,
+ * and the value provided to {@link ProgressStyle#setProgress}.
+ */
+ public int getLength() {
+ return mLength;
+ }
+
+ /**
+ * Gets the stable id of this Segment.
+ *
+ * @see #setStableId
+ */
+ public int getStableId() {
+ return mStableId;
+ }
+
+ /**
+ * Optional ID used to uniquely identify the element across updates.
+ */
+ public @NonNull Segment setStableId(int stableId) {
+ mStableId = stableId;
+ return this;
+ }
+
+ /**
+ * Returns the color of this Segment.
+ *
+ * @see #setColor
+ */
+ @ColorInt
+ public int getColor() {
+ return mColor;
+ }
+
+ /**
+ * Optional color of this Segment
+ */
+ public @NonNull Segment setColor(@ColorInt int color) {
+ mColor = color;
+ return this;
+ }
+
+ /**
+ * Needed for {@link Notification.Style#areNotificationsVisiblyDifferent}
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Segment segment = (Segment) o;
+ return mLength == segment.mLength && mStableId == segment.mStableId
+ && mColor == segment.mColor;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mLength, mStableId, mColor);
+ }
+ }
+
+ /**
+ * A step within the progress bar, defining its position and color.
+ * Steps are designated points within a progressbar to visualize
+ * distinct stages or milestones.
+ * For example, you might use steps to mark stops in a multi-stop
+ * navigation journey, where each step represents a destination.
+ */
+ public static final class Step {
+
+ private int mPosition;
+ private int mStableId;
+ @ColorInt
+ private int mColor = Notification.COLOR_DEFAULT;
+
+ /**
+ * Create a step element.
+ * The position of this step on the progress bar
+ * relative to {@link ProgressStyle#getProgressMax}
+ * @param position
+ * See {@link #getPosition}
+ */
+ public Step(int position) {
+ mPosition = position;
+ }
+
+ /**
+ * Gets the position of this Step.
+ * The position of this step on the progress bar
+ * relative to {@link ProgressStyle#getProgressMax}.
+ */
+ public int getPosition() {
+ return mPosition;
+ }
+
+
+ /**
+ * Optional ID used to uniqurely identify the element across updates.
+ */
+ public int getStableId() {
+ return mStableId;
+ }
+
+ /**
+ * Optional ID used to uniqurely identify the element across updates.
+ */
+ public @NonNull Step setStableId(int stableId) {
+ mStableId = stableId;
+ return this;
+ }
+
+ /**
+ * Returns the color of this Segment.
+ *
+ * @see #setColor
+ */
+ @ColorInt
+ public int getColor() {
+ return mColor;
+ }
+
+ /**
+ * Optional color of this Segment
+ */
+ public @NonNull Step setColor(@ColorInt int color) {
+ mColor = color;
+ return this;
+ }
+
+ /**
+ * Needed for {@link Notification.Style#areNotificationsVisiblyDifferent}
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Step step = (Step) o;
+ return mPosition == step.mPosition && mStableId == step.mStableId
+ && mColor == step.mColor;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mPosition, mStableId, mColor);
}
}
}
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index c13a58f52ac8..ea4148c8ffa1 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -230,6 +230,7 @@ import android.print.IPrintManager;
import android.print.PrintManager;
import android.provider.E2eeContactKeysManager;
import android.provider.ProviderFrameworkInitializer;
+import android.ranging.RangingFrameworkInitializer;
import android.safetycenter.SafetyCenterFrameworkInitializer;
import android.scheduling.SchedulingFrameworkInitializer;
import android.security.FileIntegrityManager;
@@ -1825,6 +1826,12 @@ public final class SystemServiceRegistry {
if (android.webkit.Flags.updateServiceIpcWrapper()) {
WebViewBootstrapFrameworkInitializer.registerServiceWrappers();
}
+ // This is guarded by aconfig flag "com.android.ranging.flags.ranging_stack_enabled"
+ // when the build flag RELEASE_RANGING_STACK is enabled. When disabled, this calls the
+ // mock RangingFrameworkInitializer#registerServiceWrappers which is no-op. As the
+ // aconfig lib for ranging module is built only if RELEASE_RANGING_STACK is enabled,
+ // flagcannot be added here.
+ RangingFrameworkInitializer.registerServiceWrappers();
} finally {
// If any of the above code throws, we're in a pretty bad shape and the process
// will likely crash, but we'll reset it just in case there's an exception handler...
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 38f59adfcc1e..c1c96eaa098d 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -299,6 +299,14 @@ public class WallpaperManager {
"android.service.wallpaper.extra.FROM_FOREGROUND_APP";
/**
+ * Extra passed on {@link Intent.ACTION_WALLPAPER_CHANGED} indicating if wallpaper was set from
+ * a foreground app.
+ * @hide
+ */
+ public static final String EXTRA_WHICH_WALLPAPER_CHANGED =
+ "android.service.wallpaper.extra.WHICH_WALLPAPER_CHANGED";
+
+ /**
* The different screen orientations. {@link #getOrientation} provides their exact definition.
* This is only used internally by the framework and the WallpaperBackupAgent.
* @hide
diff --git a/core/java/android/app/admin/PolicySizeVerifier.java b/core/java/android/app/admin/PolicySizeVerifier.java
index 7f8e50ec4420..1e03e1fd206d 100644
--- a/core/java/android/app/admin/PolicySizeVerifier.java
+++ b/core/java/android/app/admin/PolicySizeVerifier.java
@@ -22,7 +22,9 @@ import android.os.Parcelable;
import android.os.PersistableBundle;
import com.android.internal.util.Preconditions;
+import com.android.modules.utils.ModifiedUtf8;
+import java.io.UTFDataFormatException;
import java.util.ArrayDeque;
import java.util.Queue;
@@ -33,8 +35,6 @@ import java.util.Queue;
*/
public class PolicySizeVerifier {
- // Binary XML serializer doesn't support longer strings
- public static final int MAX_POLICY_STRING_LENGTH = 65535;
// FrameworkParsingPackageUtils#MAX_FILE_NAME_SIZE, Android packages are used in dir names.
public static final int MAX_PACKAGE_NAME_LENGTH = 223;
@@ -47,8 +47,11 @@ public class PolicySizeVerifier {
* Throw if string argument is too long to be serialized.
*/
public static void enforceMaxStringLength(String str, String argName) {
- Preconditions.checkArgument(
- str.length() <= MAX_POLICY_STRING_LENGTH, argName + " loo long");
+ try {
+ long len = ModifiedUtf8.countBytes(str, /* throw error if too long */ true);
+ } catch (UTFDataFormatException e) {
+ throw new IllegalArgumentException(argName + " too long");
+ }
}
/**
diff --git a/core/java/android/app/appfunctions/AppFunctionManager.java b/core/java/android/app/appfunctions/AppFunctionManager.java
index 4682f3d30e1e..6797a51e59f4 100644
--- a/core/java/android/app/appfunctions/AppFunctionManager.java
+++ b/core/java/android/app/appfunctions/AppFunctionManager.java
@@ -22,13 +22,21 @@ import static android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANA
import android.Manifest;
import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SystemService;
import android.annotation.UserHandleAware;
+import android.app.appsearch.AppSearchManager;
import android.content.Context;
+import android.os.CancellationSignal;
+import android.os.ICancellationSignal;
+import android.os.OutcomeReceiver;
+import android.os.ParcelableException;
import android.os.RemoteException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
@@ -43,10 +51,44 @@ import java.util.function.Consumer;
@FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
@SystemService(Context.APP_FUNCTION_SERVICE)
public final class AppFunctionManager {
+
+ /**
+ * The default state of the app function. Call {@link #setAppFunctionEnabled} with this to reset
+ * enabled state to the default value.
+ */
+ public static final int APP_FUNCTION_STATE_DEFAULT = 0;
+
+ /**
+ * The app function is enabled. To enable an app function, call {@link #setAppFunctionEnabled}
+ * with this value.
+ */
+ public static final int APP_FUNCTION_STATE_ENABLED = 1;
+
+ /**
+ * The app function is disabled. To disable an app function, call {@link #setAppFunctionEnabled}
+ * with this value.
+ */
+ public static final int APP_FUNCTION_STATE_DISABLED = 2;
+
private final IAppFunctionManager mService;
private final Context mContext;
/**
+ * The enabled state of the app function.
+ *
+ * @hide
+ */
+ @IntDef(
+ prefix = {"APP_FUNCTION_STATE_"},
+ value = {
+ APP_FUNCTION_STATE_DEFAULT,
+ APP_FUNCTION_STATE_ENABLED,
+ APP_FUNCTION_STATE_DISABLED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface EnabledState {}
+
+ /**
* Creates an instance.
*
* @param service An interface to the backing service.
@@ -73,7 +115,43 @@ public final class AppFunctionManager {
* android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED} or {@code
* android.permission.EXECUTE_APP_FUNCTIONS}, the execution result will contain {@code
* ExecuteAppFunctionResponse.RESULT_DENIED}.
+ * @deprecated Use {@link #executeAppFunction(ExecuteAppFunctionRequest, Executor,
+ * CancellationSignal, Consumer)} instead. This method will be removed once usage references
+ * are updated.
*/
+ @RequiresPermission(
+ anyOf = {
+ Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED,
+ Manifest.permission.EXECUTE_APP_FUNCTIONS
+ },
+ conditional = true)
+ @UserHandleAware
+ @Deprecated
+ public void executeAppFunction(
+ @NonNull ExecuteAppFunctionRequest request,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull Consumer<ExecuteAppFunctionResponse> callback) {
+ executeAppFunction(request, executor, new CancellationSignal(), callback);
+ }
+
+ /**
+ * Executes the app function.
+ *
+ * <p>Note: Applications can execute functions they define. To execute functions defined in
+ * another component, apps would need to have {@code
+ * android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED} or {@code
+ * android.permission.EXECUTE_APP_FUNCTIONS}.
+ *
+ * @param request the request to execute the app function
+ * @param executor the executor to run the callback
+ * @param cancellationSignal the cancellation signal to cancel the execution.
+ * @param callback the callback to receive the function execution result. if the calling app
+ * does not own the app function or does not have {@code
+ * android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED} or {@code
+ * android.permission.EXECUTE_APP_FUNCTIONS}, the execution result will contain {@code
+ * ExecuteAppFunctionResponse.RESULT_DENIED}.
+ */
+ // TODO(b/357551503): Document the behavior when the cancellation signal is issued.
// TODO(b/360864791): Document that apps can opt-out from being executed by callers with
// EXECUTE_APP_FUNCTIONS and how a caller knows whether a function is opted out.
// TODO(b/357551503): Update documentation when get / set APIs are implemented that this will
@@ -88,6 +166,7 @@ public final class AppFunctionManager {
public void executeAppFunction(
@NonNull ExecuteAppFunctionRequest request,
@NonNull @CallbackExecutor Executor executor,
+ @NonNull CancellationSignal cancellationSignal,
@NonNull Consumer<ExecuteAppFunctionResponse> callback) {
Objects.requireNonNull(request);
Objects.requireNonNull(executor);
@@ -96,27 +175,147 @@ public final class AppFunctionManager {
ExecuteAppFunctionAidlRequest aidlRequest =
new ExecuteAppFunctionAidlRequest(
request, mContext.getUser(), mContext.getPackageName());
+
+ try {
+ ICancellationSignal cancellationTransport =
+ mService.executeAppFunction(
+ aidlRequest,
+ new IExecuteAppFunctionCallback.Stub() {
+ @Override
+ public void onResult(ExecuteAppFunctionResponse result) {
+ try {
+ executor.execute(() -> callback.accept(result));
+ } catch (RuntimeException e) {
+ // Ideally shouldn't happen since errors are wrapped into
+ // the
+ // response, but we catch it here for additional safety.
+ callback.accept(
+ ExecuteAppFunctionResponse.newFailure(
+ getResultCode(e),
+ e.getMessage(),
+ /* extras= */ null));
+ }
+ }
+ });
+ if (cancellationTransport != null) {
+ cancellationSignal.setRemote(cancellationTransport);
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns a boolean through a callback, indicating whether the app function is enabled.
+ *
+ * <p>* This method can only check app functions owned by the caller, or those where the caller
+ * has visibility to the owner package and holds either the {@link
+ * Manifest.permission#EXECUTE_APP_FUNCTIONS} or {@link
+ * Manifest.permission#EXECUTE_APP_FUNCTIONS_TRUSTED} permission.
+ *
+ * <p>If operation fails, the callback's {@link OutcomeReceiver#onError} is called with errors:
+ *
+ * <ul>
+ * <li>{@link IllegalArgumentException}, if the function is not found or the caller does not
+ * have access to it.
+ * </ul>
+ *
+ * @param functionIdentifier the identifier of the app function to check (unique within the
+ * target package) and in most cases, these are automatically generated by the AppFunctions
+ * SDK
+ * @param targetPackage the package name of the app function's owner
+ * @param executor the executor to run the request
+ * @param callback the callback to receive the function enabled check result
+ */
+ public void isAppFunctionEnabled(
+ @NonNull String functionIdentifier,
+ @NonNull String targetPackage,
+ @NonNull Executor executor,
+ @NonNull OutcomeReceiver<Boolean, Exception> callback) {
+ Objects.requireNonNull(functionIdentifier);
+ Objects.requireNonNull(targetPackage);
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+ AppSearchManager appSearchManager = mContext.getSystemService(AppSearchManager.class);
+ if (appSearchManager == null) {
+ callback.onError(new IllegalStateException("Failed to get AppSearchManager."));
+ return;
+ }
+
+ AppFunctionManagerHelper.isAppFunctionEnabled(
+ functionIdentifier, targetPackage, appSearchManager, executor, callback);
+ }
+
+ /**
+ * Sets the enabled state of the app function owned by the calling package.
+ *
+ * <p>If operation fails, the callback's {@link OutcomeReceiver#onError} is called with errors:
+ *
+ * <ul>
+ * <li>{@link IllegalArgumentException}, if the function is not found or the caller does not
+ * have access to it.
+ * </ul>
+ *
+ * @param functionIdentifier the identifier of the app function to enable (unique within the
+ * calling package). In most cases, identifiers are automatically generated by the
+ * AppFunctions SDK
+ * @param newEnabledState the new state of the app function
+ * @param executor the executor to run the callback
+ * @param callback the callback to receive the result of the function enablement. The call was
+ * successful if no exception was thrown.
+ */
+ @UserHandleAware
+ public void setAppFunctionEnabled(
+ @NonNull String functionIdentifier,
+ @EnabledState int newEnabledState,
+ @NonNull Executor executor,
+ @NonNull OutcomeReceiver<Void, Exception> callback) {
+ Objects.requireNonNull(functionIdentifier);
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+ CallbackWrapper callbackWrapper = new CallbackWrapper(executor, callback);
try {
- mService.executeAppFunction(
- aidlRequest,
- new IExecuteAppFunctionCallback.Stub() {
- @Override
- public void onResult(ExecuteAppFunctionResponse result) {
- try {
- executor.execute(() -> callback.accept(result));
- } catch (RuntimeException e) {
- // Ideally shouldn't happen since errors are wrapped into the
- // response, but we catch it here for additional safety.
- callback.accept(
- ExecuteAppFunctionResponse.newFailure(
- getResultCode(e),
- e.getMessage(),
- /* extras= */ null));
- }
- }
- });
+ mService.setAppFunctionEnabled(
+ mContext.getPackageName(),
+ functionIdentifier,
+ mContext.getUser(),
+ newEnabledState,
+ callbackWrapper);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
+
+ private static class CallbackWrapper extends IAppFunctionEnabledCallback.Stub {
+
+ private final OutcomeReceiver<Void, Exception> mCallback;
+ private final Executor mExecutor;
+
+ CallbackWrapper(
+ @NonNull Executor callbackExecutor,
+ @NonNull OutcomeReceiver<Void, Exception> callback) {
+ mCallback = callback;
+ mExecutor = callbackExecutor;
+ }
+
+ @Override
+ public void onSuccess() {
+ mExecutor.execute(() -> mCallback.onResult(null));
+ }
+
+ @Override
+ public void onError(@NonNull ParcelableException exception) {
+ mExecutor.execute(() -> {
+ if (IllegalArgumentException.class.isAssignableFrom(
+ exception.getCause().getClass())) {
+ mCallback.onError((IllegalArgumentException) exception.getCause());
+ } else if (SecurityException.class.isAssignableFrom(
+ exception.getCause().getClass())) {
+ mCallback.onError((SecurityException) exception.getCause());
+ } else {
+ mCallback.onError(exception);
+ }
+ });
+ }
+ }
}
diff --git a/core/java/android/app/appfunctions/AppFunctionManagerHelper.java b/core/java/android/app/appfunctions/AppFunctionManagerHelper.java
index d6f45e4c9f6a..fe2db49684fd 100644
--- a/core/java/android/app/appfunctions/AppFunctionManagerHelper.java
+++ b/core/java/android/app/appfunctions/AppFunctionManagerHelper.java
@@ -22,7 +22,7 @@ import static android.app.appfunctions.AppFunctionStaticMetadataHelper.APP_FUNCT
import static android.app.appfunctions.AppFunctionStaticMetadataHelper.STATIC_PROPERTY_ENABLED_BY_DEFAULT;
import static android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER;
-import android.annotation.CallbackExecutor;
+import android.Manifest;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.app.appsearch.AppSearchManager;
@@ -33,6 +33,7 @@ import android.app.appsearch.SearchResult;
import android.app.appsearch.SearchResults;
import android.app.appsearch.SearchSpec;
import android.os.OutcomeReceiver;
+import android.text.TextUtils;
import java.io.IOException;
import java.util.List;
@@ -50,73 +51,69 @@ public class AppFunctionManagerHelper {
/**
* Returns (through a callback) a boolean indicating whether the app function is enabled.
*
- * <p>This method can only check app functions that are owned by the caller owned by packages
- * visible to the caller.
+ * This method can only check app functions owned by the caller, or those where the caller
+ * has visibility to the owner package and holds either the {@link
+ * Manifest.permission#EXECUTE_APP_FUNCTIONS} or {@link
+ * Manifest.permission#EXECUTE_APP_FUNCTIONS_TRUSTED} permission.
*
* <p>If operation fails, the callback's {@link OutcomeReceiver#onError} is called with errors:
*
* <ul>
- * <li>{@link IllegalArgumentException}, if the function is not found
- * <li>{@link SecurityException}, if the caller does not have permission to query the target
- * package
+ * <li>{@link IllegalArgumentException}, if the function is not found or the caller does not
+ * have access to it.
* </ul>
*
* @param functionIdentifier the identifier of the app function to check (unique within the
- * target package) and in most cases, these are automatically generated by the AppFunctions
- * SDK
- * @param targetPackage the package name of the app function's owner
- * @param appSearchExecutor the executor to run the metadata search mechanism through AppSearch
- * @param callbackExecutor the executor to run the callback
- * @param callback the callback to receive the function enabled check result
+ * target package) and in most cases, these are automatically
+ * generated by the AppFunctions
+ * SDK
+ * @param targetPackage the package name of the app function's owner
+ * @param executor executor the executor to run the request
+ * @param callback the callback to receive the function enabled check result
* @hide
*/
public static void isAppFunctionEnabled(
@NonNull String functionIdentifier,
@NonNull String targetPackage,
@NonNull AppSearchManager appSearchManager,
- @NonNull Executor appSearchExecutor,
- @NonNull @CallbackExecutor Executor callbackExecutor,
+ @NonNull Executor executor,
@NonNull OutcomeReceiver<Boolean, Exception> callback) {
Objects.requireNonNull(functionIdentifier);
Objects.requireNonNull(targetPackage);
Objects.requireNonNull(appSearchManager);
- Objects.requireNonNull(appSearchExecutor);
- Objects.requireNonNull(callbackExecutor);
+ Objects.requireNonNull(executor);
Objects.requireNonNull(callback);
appSearchManager.createGlobalSearchSession(
- appSearchExecutor,
+ executor,
(searchSessionResult) -> {
if (!searchSessionResult.isSuccess()) {
- callbackExecutor.execute(
- () ->
- callback.onError(
- failedResultToException(searchSessionResult)));
+ callback.onError(failedResultToException(searchSessionResult));
return;
}
try (GlobalSearchSession searchSession = searchSessionResult.getResultValue()) {
SearchResults results =
searchJoinedStaticWithRuntimeAppFunctions(
- searchSession, targetPackage, functionIdentifier);
+ Objects.requireNonNull(searchSession),
+ targetPackage,
+ functionIdentifier);
results.getNextPage(
- appSearchExecutor,
- listAppSearchResult ->
- callbackExecutor.execute(
- () -> {
- if (listAppSearchResult.isSuccess()) {
- callback.onResult(
- getEnabledStateFromSearchResults(
- Objects.requireNonNull(
- listAppSearchResult
+ executor,
+ listAppSearchResult -> {
+ if (listAppSearchResult.isSuccess()) {
+ callback.onResult(
+ getEffectiveEnabledStateFromSearchResults(
+ Objects.requireNonNull(
+ listAppSearchResult
.getResultValue())));
- } else {
- callback.onError(
- failedResultToException(
- listAppSearchResult));
- }
- }));
+ } else {
+ callback.onError(
+ failedResultToException(listAppSearchResult));
+ }
+ });
+ results.close();
} catch (Exception e) {
- callbackExecutor.execute(() -> callback.onError(e));
+ callback.onError(e);
}
});
}
@@ -124,56 +121,58 @@ public class AppFunctionManagerHelper {
/**
* Searches joined app function static and runtime metadata using the function Id and the
* package.
- *
- * @hide
*/
private static @NonNull SearchResults searchJoinedStaticWithRuntimeAppFunctions(
@NonNull GlobalSearchSession session,
@NonNull String targetPackage,
@NonNull String functionIdentifier) {
SearchSpec runtimeSearchSpec =
- getAppFunctionRuntimeMetadataSearchSpecByFunctionId(targetPackage);
+ getAppFunctionRuntimeMetadataSearchSpecByPackageName(targetPackage);
JoinSpec joinSpec =
new JoinSpec.Builder(PROPERTY_APP_FUNCTION_STATIC_METADATA_QUALIFIED_ID)
- .setNestedSearch(functionIdentifier, runtimeSearchSpec)
+ .setNestedSearch(
+ buildFilerRuntimeMetadataByFunctionIdQuery(functionIdentifier),
+ runtimeSearchSpec)
.build();
SearchSpec joinedStaticWithRuntimeSearchSpec =
new SearchSpec.Builder()
- .setJoinSpec(joinSpec)
.addFilterPackageNames(APP_FUNCTION_INDEXER_PACKAGE)
.addFilterSchemas(
AppFunctionStaticMetadataHelper.getStaticSchemaNameForPackage(
targetPackage))
- .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .setJoinSpec(joinSpec)
+ .setVerbatimSearchEnabled(true)
.build();
- return session.search(functionIdentifier, joinedStaticWithRuntimeSearchSpec);
+ return session.search(
+ buildFilerStaticMetadataByFunctionIdQuery(functionIdentifier),
+ joinedStaticWithRuntimeSearchSpec);
}
/**
- * Finds whether the function is enabled or not from the search results returned by {@link
- * #searchJoinedStaticWithRuntimeAppFunctions}.
+ * Returns whether the function is effectively enabled or not from the search results returned
+ * by {@link #searchJoinedStaticWithRuntimeAppFunctions}.
*
+ * @param joinedStaticRuntimeResults search results joining AppFunctionStaticMetadata
+ * and AppFunctionRuntimeMetadata.
* @throws IllegalArgumentException if the function is not found in the results
- * @hide
*/
- private static boolean getEnabledStateFromSearchResults(
+ private static boolean getEffectiveEnabledStateFromSearchResults(
@NonNull List<SearchResult> joinedStaticRuntimeResults) {
if (joinedStaticRuntimeResults.isEmpty()) {
- // Function not found.
throw new IllegalArgumentException("App function not found.");
} else {
List<SearchResult> runtimeMetadataResults =
joinedStaticRuntimeResults.getFirst().getJoinedResults();
- if (!runtimeMetadataResults.isEmpty()) {
- Boolean result =
- (Boolean)
- runtimeMetadataResults
- .getFirst()
- .getGenericDocument()
- .getProperty(PROPERTY_ENABLED);
- if (result != null) {
- return result;
- }
+ if (runtimeMetadataResults.isEmpty()) {
+ throw new IllegalArgumentException("App function not found.");
+ }
+ boolean[] enabled =
+ runtimeMetadataResults
+ .getFirst()
+ .getGenericDocument()
+ .getPropertyBooleanArray(PROPERTY_ENABLED);
+ if (enabled != null && enabled.length != 0) {
+ return enabled[0];
}
// Runtime metadata not found. Using the default value in the static metadata.
return joinedStaticRuntimeResults
@@ -186,36 +185,39 @@ public class AppFunctionManagerHelper {
/**
* Returns search spec that queries app function metadata for a specific package name by its
* function identifier.
- *
- * @hide
*/
- public static @NonNull SearchSpec getAppFunctionRuntimeMetadataSearchSpecByFunctionId(
+ private static @NonNull SearchSpec getAppFunctionRuntimeMetadataSearchSpecByPackageName(
@NonNull String targetPackage) {
return new SearchSpec.Builder()
.addFilterPackageNames(APP_FUNCTION_INDEXER_PACKAGE)
.addFilterSchemas(
AppFunctionRuntimeMetadata.getRuntimeSchemaNameForPackage(targetPackage))
- .addFilterProperties(
- AppFunctionRuntimeMetadata.getRuntimeSchemaNameForPackage(targetPackage),
- List.of(AppFunctionRuntimeMetadata.PROPERTY_FUNCTION_ID))
- .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .setVerbatimSearchEnabled(true)
.build();
}
- /**
- * Converts a failed app search result codes into an exception.
- *
- * @hide
- */
- public static @NonNull Exception failedResultToException(
+ private static String buildFilerRuntimeMetadataByFunctionIdQuery(String functionIdentifier) {
+ return TextUtils.formatSimple("%s:\"%s\"",
+ AppFunctionRuntimeMetadata.PROPERTY_FUNCTION_ID,
+ functionIdentifier);
+ }
+
+ private static String buildFilerStaticMetadataByFunctionIdQuery(String functionIdentifier) {
+ return TextUtils.formatSimple("%s:\"%s\"",
+ AppFunctionStaticMetadataHelper.PROPERTY_FUNCTION_ID,
+ functionIdentifier);
+ }
+
+ /** Converts a failed app search result codes into an exception. */
+ private static @NonNull Exception failedResultToException(
@NonNull AppSearchResult appSearchResult) {
return switch (appSearchResult.getResultCode()) {
- case AppSearchResult.RESULT_INVALID_ARGUMENT ->
- new IllegalArgumentException(appSearchResult.getErrorMessage());
- case AppSearchResult.RESULT_IO_ERROR ->
- new IOException(appSearchResult.getErrorMessage());
- case AppSearchResult.RESULT_SECURITY_ERROR ->
- new SecurityException(appSearchResult.getErrorMessage());
+ case AppSearchResult.RESULT_INVALID_ARGUMENT -> new IllegalArgumentException(
+ appSearchResult.getErrorMessage());
+ case AppSearchResult.RESULT_IO_ERROR -> new IOException(
+ appSearchResult.getErrorMessage());
+ case AppSearchResult.RESULT_SECURITY_ERROR -> new SecurityException(
+ appSearchResult.getErrorMessage());
default -> new IllegalStateException(appSearchResult.getErrorMessage());
};
}
diff --git a/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java b/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java
index 83b5aa05c383..8b7f326ee816 100644
--- a/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java
+++ b/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java
@@ -204,11 +204,17 @@ public class AppFunctionRuntimeMetadata extends GenericDocument {
packageName, functionId));
}
+ public Builder(AppFunctionRuntimeMetadata original) {
+ this(original.getPackageName(), original.getFunctionId());
+ setEnabled(original.getEnabled());
+ }
+
/**
* Sets an indicator specifying if the function is enabled or not. This would override the
* default enabled state in the static metadata ({@link
- * AppFunctionStaticMetadataHelper#STATIC_PROPERTY_ENABLED_BY_DEFAULT}). Sets this to
- * null to clear the override.
+ * AppFunctionStaticMetadataHelper#STATIC_PROPERTY_ENABLED_BY_DEFAULT}). Sets this to null
+ * to clear the override.
+ * TODO(369683073) Replace the tristate Boolean with IntDef EnabledState.
*/
@NonNull
public Builder setEnabled(@Nullable Boolean enabled) {
diff --git a/core/java/android/app/appfunctions/AppFunctionService.java b/core/java/android/app/appfunctions/AppFunctionService.java
index 0d981ea5a679..8e417737515e 100644
--- a/core/java/android/app/appfunctions/AppFunctionService.java
+++ b/core/java/android/app/appfunctions/AppFunctionService.java
@@ -29,7 +29,12 @@ import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
+import android.os.Bundle;
import android.os.IBinder;
+import android.os.ICancellationSignal;
+import android.os.CancellationSignal;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
import java.util.function.Consumer;
@@ -74,6 +79,7 @@ public abstract class AppFunctionService extends Service {
*/
void perform(
@NonNull ExecuteAppFunctionRequest request,
+ @NonNull CancellationSignal cancellationSignal,
@NonNull Consumer<ExecuteAppFunctionResponse> callback);
}
@@ -85,6 +91,7 @@ public abstract class AppFunctionService extends Service {
@Override
public void executeAppFunction(
@NonNull ExecuteAppFunctionRequest request,
+ @NonNull ICancellationCallback cancellationCallback,
@NonNull IExecuteAppFunctionCallback callback) {
if (context.checkCallingPermission(BIND_APP_FUNCTION_SERVICE)
== PERMISSION_DENIED) {
@@ -93,7 +100,10 @@ public abstract class AppFunctionService extends Service {
SafeOneTimeExecuteAppFunctionCallback safeCallback =
new SafeOneTimeExecuteAppFunctionCallback(callback);
try {
- onExecuteFunction.perform(request, safeCallback::onResult);
+ onExecuteFunction.perform(
+ request,
+ buildCancellationSignal(cancellationCallback),
+ safeCallback::onResult);
} catch (Exception ex) {
// Apps should handle exceptions. But if they don't, report the error on
// behalf of them.
@@ -105,6 +115,21 @@ public abstract class AppFunctionService extends Service {
};
}
+ private static CancellationSignal buildCancellationSignal(
+ @NonNull ICancellationCallback cancellationCallback) {
+ final ICancellationSignal cancellationSignalTransport =
+ CancellationSignal.createTransport();
+ CancellationSignal cancellationSignal =
+ CancellationSignal.fromTransport(cancellationSignalTransport);
+ try {
+ cancellationCallback.sendCancellationTransport(cancellationSignalTransport);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+
+ return cancellationSignal ;
+ }
+
private final Binder mBinder = createBinder(
AppFunctionService.this,
AppFunctionService.this::onExecuteFunction);
@@ -115,6 +140,7 @@ public abstract class AppFunctionService extends Service {
return mBinder;
}
+
/**
* Called by the system to execute a specific app function.
*
@@ -134,9 +160,45 @@ public abstract class AppFunctionService extends Service {
*
* @param request The function execution request.
* @param callback A callback to report back the result.
+ *
+ * @deprecated Use {@link #onExecuteFunction(ExecuteAppFunctionRequest, CancellationSignal,
+ * Consumer)} instead. This method will be removed once usage references are updated.
*/
@MainThread
+ @Deprecated
public abstract void onExecuteFunction(
@NonNull ExecuteAppFunctionRequest request,
@NonNull Consumer<ExecuteAppFunctionResponse> callback);
+
+ /**
+ * Called by the system to execute a specific app function.
+ *
+ * <p>This method is triggered when the system requests your AppFunctionService to handle a
+ * particular function you have registered and made available.
+ *
+ * <p>To ensure proper routing of function requests, assign a unique identifier to each
+ * function. This identifier doesn't need to be globally unique, but it must be unique within
+ * your app. For example, a function to order food could be identified as "orderFood". In most
+ * cases this identifier should come from the ID automatically generated by the AppFunctions
+ * SDK. You can determine the specific function to invoke by calling {@link
+ * ExecuteAppFunctionRequest#getFunctionIdentifier()}.
+ *
+ * <p>This method is always triggered in the main thread. You should run heavy tasks on a worker
+ * thread and dispatch the result with the given callback. You should always report back the
+ * result using the callback, no matter if the execution was successful or not.
+ *
+ * <p>This method also accepts a {@link CancellationSignal} that the app should listen to cancel
+ * the execution of function if requested by the system.
+ *
+ * @param request The function execution request.
+ * @param cancellationSignal A signal to cancel the execution.
+ * @param callback A callback to report back the result.
+ */
+ @MainThread
+ public void onExecuteFunction(
+ @NonNull ExecuteAppFunctionRequest request,
+ @NonNull CancellationSignal cancellationSignal,
+ @NonNull Consumer<ExecuteAppFunctionResponse> callback) {
+ onExecuteFunction(request, callback);
+ }
}
diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
index f6580e63d757..4ed0a1b50a08 100644
--- a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
+++ b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
@@ -99,6 +99,9 @@ public final class ExecuteAppFunctionResponse implements Parcelable {
/** The operation was timed out. */
public static final int RESULT_TIMED_OUT = 5;
+ /** The caller tried to execute a disabled app function. */
+ public static final int RESULT_DISABLED = 6;
+
/** The result code of the app function execution. */
@ResultCode private final int mResultCode;
@@ -274,6 +277,7 @@ public final class ExecuteAppFunctionResponse implements Parcelable {
RESULT_INTERNAL_ERROR,
RESULT_INVALID_ARGUMENT,
RESULT_TIMED_OUT,
+ RESULT_DISABLED,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ResultCode {}
diff --git a/core/java/android/app/appfunctions/GenericDocumentWrapper.java b/core/java/android/app/appfunctions/GenericDocumentWrapper.java
index 84b1837f4a2f..b29b64e44d21 100644
--- a/core/java/android/app/appfunctions/GenericDocumentWrapper.java
+++ b/core/java/android/app/appfunctions/GenericDocumentWrapper.java
@@ -16,10 +16,13 @@
package android.app.appfunctions;
+import android.annotation.Nullable;
import android.app.appsearch.GenericDocument;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.MathUtils;
+import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
import java.util.Objects;
@@ -31,24 +34,33 @@ import java.util.Objects;
* <p>{#link {@link Parcel#writeBlob(byte[])}} could take care of whether to pass data via binder
* directly or Android shared memory if the data is large.
*
+ * <p>This class performs lazy unparcelling. The `GenericDocument` is only unparcelled
+ * from the underlying `Parcel` when {@link #getValue()} is called. This optimization
+ * allows the system server to pass through the generic document, without unparcel and parcel it.
+ *
* @hide
* @see Parcel#writeBlob(byte[])
*/
public final class GenericDocumentWrapper implements Parcelable {
+ @Nullable
+ @GuardedBy("mLock")
+ private GenericDocument mGenericDocument;
+ @GuardedBy("mLock")
+ @Nullable private Parcel mParcel;
+ private final Object mLock = new Object();
+
public static final Creator<GenericDocumentWrapper> CREATOR =
new Creator<>() {
@Override
public GenericDocumentWrapper createFromParcel(Parcel in) {
- byte[] dataBlob = Objects.requireNonNull(in.readBlob());
- Parcel unmarshallParcel = Parcel.obtain();
- try {
- unmarshallParcel.unmarshall(dataBlob, 0, dataBlob.length);
- unmarshallParcel.setDataPosition(0);
- return new GenericDocumentWrapper(
- GenericDocument.createFromParcel(unmarshallParcel));
- } finally {
- unmarshallParcel.recycle();
- }
+ int length = in.readInt();
+ int offset = in.dataPosition();
+ in.setDataPosition(MathUtils.addOrThrow(offset, length));
+
+ Parcel p = Parcel.obtain();
+ p.appendFrom(in, offset, length);
+ p.setDataPosition(0);
+ return new GenericDocumentWrapper(p);
}
@Override
@@ -56,16 +68,42 @@ public final class GenericDocumentWrapper implements Parcelable {
return new GenericDocumentWrapper[size];
}
};
- @NonNull private final GenericDocument mGenericDocument;
public GenericDocumentWrapper(@NonNull GenericDocument genericDocument) {
mGenericDocument = Objects.requireNonNull(genericDocument);
+ mParcel = null;
+ }
+
+ public GenericDocumentWrapper(@NonNull Parcel parcel) {
+ mGenericDocument = null;
+ mParcel = Objects.requireNonNull(parcel);
}
/** Returns the wrapped {@link android.app.appsearch.GenericDocument} */
@NonNull
public GenericDocument getValue() {
- return mGenericDocument;
+ unparcel();
+ synchronized (mLock) {
+ return Objects.requireNonNull(mGenericDocument);
+ }
+ }
+
+ private void unparcel() {
+ synchronized (mLock) {
+ if (mGenericDocument != null) {
+ return;
+ }
+ byte[] dataBlob = Objects.requireNonNull(Objects.requireNonNull(mParcel).readBlob());
+ Parcel unmarshallParcel = Parcel.obtain();
+ try {
+ unmarshallParcel.unmarshall(dataBlob, 0, dataBlob.length);
+ unmarshallParcel.setDataPosition(0);
+ mGenericDocument = GenericDocument.createFromParcel(unmarshallParcel);
+ mParcel = null;
+ } finally {
+ unmarshallParcel.recycle();
+ }
+ }
}
@Override
@@ -75,13 +113,32 @@ public final class GenericDocumentWrapper implements Parcelable {
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- Parcel parcel = Parcel.obtain();
- try {
- mGenericDocument.writeToParcel(parcel, flags);
- byte[] bytes = parcel.marshall();
- dest.writeBlob(bytes);
- } finally {
- parcel.recycle();
+ synchronized (mLock) {
+ if (mGenericDocument != null) {
+ int lengthPos = dest.dataPosition();
+ // write a placeholder for length
+ dest.writeInt(-1);
+ Parcel tempParcel = Parcel.obtain();
+ byte[] bytes;
+ try {
+ mGenericDocument.writeToParcel(tempParcel, flags);
+ bytes = tempParcel.marshall();
+ } finally {
+ tempParcel.recycle();
+ }
+ int startPos = dest.dataPosition();
+ dest.writeBlob(bytes);
+ int endPos = dest.dataPosition();
+ dest.setDataPosition(lengthPos);
+ // Overwrite the length placeholder
+ dest.writeInt(endPos - startPos);
+ dest.setDataPosition(endPos);
+
+ } else {
+ Parcel originalParcel = Objects.requireNonNull(mParcel);
+ dest.writeInt(originalParcel.dataSize());
+ dest.appendFrom(originalParcel, 0, originalParcel.dataSize());
+ }
}
}
}
diff --git a/core/java/android/app/appfunctions/IAppFunctionEnabledCallback.aidl b/core/java/android/app/appfunctions/IAppFunctionEnabledCallback.aidl
new file mode 100644
index 000000000000..ced415541e49
--- /dev/null
+++ b/core/java/android/app/appfunctions/IAppFunctionEnabledCallback.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.appfunctions;
+
+import android.os.ParcelableException;
+
+/**
+ * @hide
+ */
+oneway interface IAppFunctionEnabledCallback {
+ void onSuccess();
+ void onError(in ParcelableException exception);
+}
diff --git a/core/java/android/app/appfunctions/IAppFunctionManager.aidl b/core/java/android/app/appfunctions/IAppFunctionManager.aidl
index 28827bb3052c..72335e40c207 100644
--- a/core/java/android/app/appfunctions/IAppFunctionManager.aidl
+++ b/core/java/android/app/appfunctions/IAppFunctionManager.aidl
@@ -17,8 +17,11 @@
package android.app.appfunctions;
import android.app.appfunctions.ExecuteAppFunctionAidlRequest;
+import android.app.appfunctions.IAppFunctionEnabledCallback;
import android.app.appfunctions.IExecuteAppFunctionCallback;
+import android.os.ICancellationSignal;
+import android.os.UserHandle;
/**
* Defines the interface for apps to interact with the app function execution service
* {@code AppFunctionManagerService} running in the system server process.
@@ -32,8 +35,19 @@ interface IAppFunctionManager {
* @param callback the callback to report the result.
*/
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf = {android.Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED,android.Manifest.permission.EXECUTE_APP_FUNCTIONS}, conditional = true)")
- void executeAppFunction(
+ ICancellationSignal executeAppFunction(
in ExecuteAppFunctionAidlRequest request,
in IExecuteAppFunctionCallback callback
);
-} \ No newline at end of file
+
+ /**
+ * Sets an AppFunction's enabled state provided by {@link AppFunctionService} through the system.
+ */
+ void setAppFunctionEnabled(
+ in String callingPackage,
+ in String functionIdentifier,
+ in UserHandle userHandle,
+ int enabledState,
+ in IAppFunctionEnabledCallback callback
+ );
+}
diff --git a/core/java/android/app/appfunctions/IAppFunctionService.aidl b/core/java/android/app/appfunctions/IAppFunctionService.aidl
index cc5a20cfa194..291f33ccb1b8 100644
--- a/core/java/android/app/appfunctions/IAppFunctionService.aidl
+++ b/core/java/android/app/appfunctions/IAppFunctionService.aidl
@@ -16,7 +16,7 @@
package android.app.appfunctions;
-import android.os.Bundle;
+import android.app.appfunctions.ICancellationCallback;
import android.app.appfunctions.IExecuteAppFunctionCallback;
import android.app.appfunctions.ExecuteAppFunctionRequest;
@@ -34,10 +34,12 @@ oneway interface IAppFunctionService {
* Called by the system to execute a specific app function.
*
* @param request the function execution request.
+ * @param cancellationCallback a callback to send back the cancellation transport.
* @param callback a callback to report back the result.
*/
void executeAppFunction(
in ExecuteAppFunctionRequest request,
+ in ICancellationCallback cancellationCallback,
in IExecuteAppFunctionCallback callback
);
}
diff --git a/core/java/android/app/appfunctions/ICancellationCallback.aidl b/core/java/android/app/appfunctions/ICancellationCallback.aidl
new file mode 100644
index 000000000000..03235aca017a
--- /dev/null
+++ b/core/java/android/app/appfunctions/ICancellationCallback.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.appfunctions;
+
+import android.os.ICancellationSignal;
+
+/** {@hide} */
+oneway interface ICancellationCallback {
+ void sendCancellationTransport(in ICancellationSignal cancellationTransport);
+} \ No newline at end of file
diff --git a/core/java/android/app/wallpaper.aconfig b/core/java/android/app/wallpaper.aconfig
index 409162202b59..c5bd56ff67aa 100644
--- a/core/java/android/app/wallpaper.aconfig
+++ b/core/java/android/app/wallpaper.aconfig
@@ -1,8 +1,16 @@
package: "android.app"
container: "system"
+
flag {
name: "remove_next_wallpaper_component"
namespace: "systemui"
description: "Remove deprecated field WallpaperData#nextWallpaperComponent. Only effective after rebooting."
bug: "365991991"
}
+
+flag {
+ name: "fix_wallpaper_changed"
+ namespace: "systemui"
+ description: "Fixes timing of wallpaper changed notification and adds extra information. Only effective after rebooting."
+ bug: "369814294"
+}
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index 60b409ac6346..160cbdffe5bb 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -301,4 +301,11 @@ flag {
description: "Feature flag to remove hack code of using PackageManager.MATCH_ANY_USER flag without cross user permission."
bug: "332664521"
is_fixed_read_only: true
-} \ No newline at end of file
+}
+
+flag {
+ name: "delete_packages_silently_backport"
+ namespace: "package_manager_service"
+ description: "Feature flag to enable the holder of SYSTEM_APP_PROTECTION_SERVICE role to silently delete packages. To be deprecated by delete_packages_silently."
+ bug: "361776825"
+}
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 21627920f598..1b21bdf7ba45 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -181,7 +181,7 @@ public final class CameraManager {
* @hide
*/
@TestApi
- @FlaggedApi(com.android.window.flags.Flags.FLAG_CAMERA_COMPAT_FOR_FREEFORM)
+ @FlaggedApi(com.android.window.flags.Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
public static final int ROTATION_OVERRIDE_NONE = ICameraService.ROTATION_OVERRIDE_NONE;
/**
@@ -191,7 +191,7 @@ public final class CameraManager {
* @hide
*/
@TestApi
- @FlaggedApi(com.android.window.flags.Flags.FLAG_CAMERA_COMPAT_FOR_FREEFORM)
+ @FlaggedApi(com.android.window.flags.Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
public static final int ROTATION_OVERRIDE_OVERRIDE_TO_PORTRAIT =
ICameraService.ROTATION_OVERRIDE_OVERRIDE_TO_PORTRAIT;
@@ -201,7 +201,7 @@ public final class CameraManager {
* @hide
*/
@TestApi
- @FlaggedApi(com.android.window.flags.Flags.FLAG_CAMERA_COMPAT_FOR_FREEFORM)
+ @FlaggedApi(com.android.window.flags.Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
public static final int ROTATION_OVERRIDE_ROTATION_ONLY =
ICameraService.ROTATION_OVERRIDE_ROTATION_ONLY;
@@ -1562,7 +1562,7 @@ public final class CameraManager {
*/
public static int getRotationOverride(@Nullable Context context,
@Nullable PackageManager packageManager, @Nullable String packageName) {
- if (com.android.window.flags.Flags.cameraCompatForFreeform()) {
+ if (com.android.window.flags.Flags.enableCameraCompatForDesktopWindowing()) {
return getRotationOverrideInternal(context, packageManager, packageName);
} else {
return shouldOverrideToPortrait(packageManager, packageName)
@@ -1574,7 +1574,7 @@ public final class CameraManager {
/**
* @hide
*/
- @FlaggedApi(com.android.window.flags.Flags.FLAG_CAMERA_COMPAT_FOR_FREEFORM)
+ @FlaggedApi(com.android.window.flags.Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
@TestApi
public static int getRotationOverrideInternal(@Nullable Context context,
@Nullable PackageManager packageManager, @Nullable String packageName) {
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index 79cbd19b248d..05e91e447a43 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -1529,6 +1529,8 @@ public class SoundTrigger {
* config that can be used by
* {@link SoundTriggerModule#startRecognition(int, RecognitionConfig)}
*
+ * @deprecated should use builder-based constructor instead.
+ * TODO(b/368042125): remove this method.
* @param captureRequested Whether the DSP should capture the trigger sound.
* @param allowMultipleTriggers Whether the service should restart listening after the DSP
* triggers.
@@ -1539,6 +1541,8 @@ public class SoundTrigger {
*
* @hide
*/
+ @Deprecated
+ @SuppressWarnings("Todo")
@TestApi
public RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers,
@SuppressLint("ArrayReturn") @Nullable KeyphraseRecognitionExtra[] keyphrases,
@@ -1695,6 +1699,89 @@ public class SoundTrigger {
result = prime * result + mAudioCapabilities;
return result;
}
+
+ /**
+ * Builder class for {@link RecognitionConfig} objects.
+ */
+ public static final class Builder {
+ private boolean mCaptureRequested;
+ private boolean mAllowMultipleTriggers;
+ @Nullable private KeyphraseRecognitionExtra[] mKeyphrases;
+ @Nullable private byte[] mData;
+ private int mAudioCapabilities;
+
+ /**
+ * Constructs a new Builder with the default values.
+ */
+ public Builder() {
+ }
+
+ /**
+ * Sets capture requested state.
+ * @param captureRequested The new requested state.
+ * @return the same Builder instance.
+ */
+ public @NonNull Builder setCaptureRequested(boolean captureRequested) {
+ mCaptureRequested = captureRequested;
+ return this;
+ }
+
+ /**
+ * Sets allow multiple triggers state.
+ * @param allowMultipleTriggers The new allow multiple triggers state.
+ * @return the same Builder instance.
+ */
+ public @NonNull Builder setAllowMultipleTriggers(boolean allowMultipleTriggers) {
+ mAllowMultipleTriggers = allowMultipleTriggers;
+ return this;
+ }
+
+ /**
+ * Sets the keyphrases field.
+ * @param keyphrases The new keyphrases.
+ * @return the same Builder instance.
+ */
+ public @NonNull Builder setKeyphrases(
+ @NonNull Collection<KeyphraseRecognitionExtra> keyphrases) {
+ mKeyphrases = keyphrases.toArray(new KeyphraseRecognitionExtra[keyphrases.size()]);
+ return this;
+ }
+
+ /**
+ * Sets the data field.
+ * @param data The new data.
+ * @return the same Builder instance.
+ */
+ public @NonNull Builder setData(@Nullable byte[] data) {
+ mData = data;
+ return this;
+ }
+
+ /**
+ * Sets the audio capabilities field.
+ * @param audioCapabilities The new audio capabilities.
+ * @return the same Builder instance.
+ */
+ public @NonNull Builder setAudioCapabilities(int audioCapabilities) {
+ mAudioCapabilities = audioCapabilities;
+ return this;
+ }
+
+ /**
+ * Combines all of the parameters that have been set and return a new
+ * {@link RecognitionConfig} object.
+ * @return a new {@link RecognitionConfig} object
+ */
+ public @NonNull RecognitionConfig build() {
+ RecognitionConfig config = new RecognitionConfig(
+ /* captureRequested= */ mCaptureRequested,
+ /* allowMultipleTriggers= */ mAllowMultipleTriggers,
+ /* keyphrases= */ mKeyphrases,
+ /* data= */ mData,
+ /* audioCapabilities= */ mAudioCapabilities);
+ return config;
+ }
+ };
}
/**
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index b8a8be159d12..d82af55e2771 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -10750,6 +10750,16 @@ public final class Settings {
"lock_screen_show_only_unseen_notifications";
/**
+ * Indicates whether to minimalize the number of notifications to show on the lockscreen.
+ * <p>
+ * Type: int (0 for false, 1 for true)
+ *
+ * @hide
+ */
+ public static final String LOCK_SCREEN_NOTIFICATION_MINIMALISM =
+ "lock_screen_notification_minimalism";
+
+ /**
* Indicates whether snooze options should be shown on notifications
* <p>
* Type: int (0 for false, 1 for true)
diff --git a/core/java/android/ranging/mock/RangingFrameworkInitializer.java b/core/java/android/ranging/mock/RangingFrameworkInitializer.java
new file mode 100644
index 000000000000..540f51954a9c
--- /dev/null
+++ b/core/java/android/ranging/mock/RangingFrameworkInitializer.java
@@ -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 android.ranging;
+
+/**
+* Mock RangingFrameworkInitializer.
+*
+* @hide
+*/
+
+// TODO(b/331206299): Remove this after RANGING_STACK_ENABLED is ramped up to next.
+public final class RangingFrameworkInitializer {
+ private RangingFrameworkInitializer() {}
+ /**
+ * @hide
+ */
+ public static void registerServiceWrappers() {
+ // No-op.
+ }
+}
diff --git a/core/java/android/security/responsible_apis_flags.aconfig b/core/java/android/security/responsible_apis_flags.aconfig
index 45e9def2c15f..5457bbee8ad3 100644
--- a/core/java/android/security/responsible_apis_flags.aconfig
+++ b/core/java/android/security/responsible_apis_flags.aconfig
@@ -24,6 +24,17 @@ flag {
}
flag {
+ name: "asm_reintroduce_grace_period"
+ namespace: "responsible_apis"
+ description: "Allow launches within the grace period for ASM apps"
+ bug: "367702727"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+
+flag {
name: "content_uri_permission_apis"
is_exported: true
namespace: "responsible_apis"
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 303197dfd82d..e1732559e262 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -1079,9 +1079,12 @@ public class ZenModeConfig implements Parcelable {
// in ensureManualZenRule() and setManualZenMode().
rt.manualRule.pkg = PACKAGE_ANDROID;
rt.manualRule.type = AutomaticZenRule.TYPE_OTHER;
- rt.manualRule.condition = new Condition(
- rt.manualRule.conditionId != null ? rt.manualRule.conditionId
- : Uri.EMPTY, "", Condition.STATE_TRUE);
+ // conditionId in rule must match condition.id to pass isValidManualRule().
+ if (rt.manualRule.conditionId == null) {
+ rt.manualRule.conditionId = Uri.EMPTY;
+ }
+ rt.manualRule.condition = new Condition(rt.manualRule.conditionId, "",
+ Condition.STATE_TRUE);
}
}
return rt;
diff --git a/core/java/android/view/OWNERS b/core/java/android/view/OWNERS
index 31a8dfaaf86b..1ea58bcbb76a 100644
--- a/core/java/android/view/OWNERS
+++ b/core/java/android/view/OWNERS
@@ -48,6 +48,7 @@ per-file KeyCharacterMap.java = file:/services/core/java/com/android/server/inpu
per-file VelocityTracker.java = file:/services/core/java/com/android/server/input/OWNERS
per-file VerifiedInputEvent.java = file:/services/core/java/com/android/server/input/OWNERS
per-file VerifiedInputEvent.aidl = file:/services/core/java/com/android/server/input/OWNERS
+per-file LetterboxScrollProcessor*.java = file:/services/core/java/com/android/server/input/OWNERS
# InputWindowHandle
per-file InputWindowHandle.java = file:/services/core/java/com/android/server/input/OWNERS
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 66776ce04ad0..ac208b57788d 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -2064,7 +2064,11 @@ public final class ViewRootImpl implements ViewParent,
if (mAttachInfo.mThreadedRenderer == null) return;
if (mAttachInfo.mThreadedRenderer.setForceDark(determineForceDarkType())) {
// TODO: Don't require regenerating all display lists to apply this setting
- invalidateWorld(mView);
+ if (forceInvertColor()) {
+ destroyAndInvalidate();
+ } else {
+ invalidateWorld(mView);
+ }
}
}
@@ -11911,15 +11915,23 @@ public final class ViewRootImpl implements ViewParent,
public void onHighTextContrastStateChanged(boolean enabled) {
ThreadedRenderer.setHighContrastText(enabled);
- // Destroy Displaylists so they can be recreated with high contrast recordings
- destroyHardwareResources();
-
- // Schedule redraw, which will rerecord + redraw all text
- invalidate();
+ destroyAndInvalidate();
}
}
/**
+ * Destroy Displaylists so they can be recreated with new recordings, in case you are changing
+ * the way things are rendered (e.g. high contrast, force dark), then invalidate to trigger a
+ * redraw.
+ */
+ private void destroyAndInvalidate() {
+ destroyHardwareResources();
+
+ // Schedule redraw, which will rerecord + redraw all text
+ invalidate();
+ }
+
+ /**
* This class is an interface this ViewAncestor provides to the
* AccessibilityManagerService to the latter can interact with
* the view hierarchy in this ViewAncestor.
diff --git a/core/java/android/view/WindowLayout.java b/core/java/android/view/WindowLayout.java
index dda399357d8c..d5ccca992b4f 100644
--- a/core/java/android/view/WindowLayout.java
+++ b/core/java/android/view/WindowLayout.java
@@ -157,10 +157,10 @@ public class WindowLayout {
// which prevents overlap with the DisplayCutout.
if (!attachedInParent && !floatingInScreenWindow) {
mTempRect.set(outParentFrame);
- outParentFrame.intersectUnchecked(displayCutoutSafeExceptMaybeBars);
+ intersectOrClamp(outParentFrame, displayCutoutSafeExceptMaybeBars);
frames.isParentFrameClippedByDisplayCutout = !mTempRect.equals(outParentFrame);
}
- outDisplayFrame.intersectUnchecked(displayCutoutSafeExceptMaybeBars);
+ intersectOrClamp(outDisplayFrame, displayCutoutSafeExceptMaybeBars);
}
final boolean noLimits = (attrs.flags & FLAG_LAYOUT_NO_LIMITS) != 0;
@@ -283,6 +283,19 @@ public class WindowLayout {
+ " requestedInvisibleTypes=" + WindowInsets.Type.toString(~requestedVisibleTypes));
}
+ /**
+ * If both rectangles intersect, set inOutRect to that intersection. Otherwise, clamp inOutRect
+ * to the side (or the corner) that the other rectangle is away from.
+ * Unlike {@link Rect#intersectUnchecked(Rect)}, this method guarantees that the new rectangle
+ * is valid and contained in inOutRect if rectangles involved are valid.
+ */
+ private static void intersectOrClamp(Rect inOutRect, Rect other) {
+ inOutRect.left = Math.min(Math.max(inOutRect.left, other.left), inOutRect.right);
+ inOutRect.top = Math.min(Math.max(inOutRect.top, other.top), inOutRect.bottom);
+ inOutRect.right = Math.max(Math.min(inOutRect.right, other.right), inOutRect.left);
+ inOutRect.bottom = Math.max(Math.min(inOutRect.bottom, other.bottom), inOutRect.top);
+ }
+
public static void extendFrameByCutout(Rect displayCutoutSafe,
Rect displayFrame, Rect inOutFrame, Rect tempRect) {
if (displayCutoutSafe.contains(inOutFrame)) {
diff --git a/core/java/android/view/accessibility/AccessibilityWindowInfo.java b/core/java/android/view/accessibility/AccessibilityWindowInfo.java
index c92593f81558..7b6e070f0008 100644
--- a/core/java/android/view/accessibility/AccessibilityWindowInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityWindowInfo.java
@@ -880,10 +880,6 @@ public final class AccessibilityWindowInfo implements Parcelable {
* @hide
*/
public static String typeToString(int type) {
- if (Flags.enableTypeWindowControl() && type == TYPE_WINDOW_CONTROL) {
- return "TYPE_WINDOW_CONTROL";
- }
-
switch (type) {
case TYPE_APPLICATION: {
return "TYPE_APPLICATION";
@@ -903,8 +899,12 @@ public final class AccessibilityWindowInfo implements Parcelable {
case TYPE_MAGNIFICATION_OVERLAY: {
return "TYPE_MAGNIFICATION_OVERLAY";
}
- default:
+ default: {
+ if (Flags.enableTypeWindowControl() && type == TYPE_WINDOW_CONTROL) {
+ return "TYPE_WINDOW_CONTROL";
+ }
return "<UNKNOWN:" + type + ">";
+ }
}
}
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 2f649c21fe08..1e5c6d8177e1 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -465,13 +465,6 @@ public final class InputMethodManager {
private static final long USE_ASYNC_SHOW_HIDE_METHOD = 352594277L; // This is a bug id.
/**
- * Version-gating is guarded by bug-fix flag.
- */
- private static final boolean ASYNC_SHOW_HIDE_METHOD_ENABLED =
- !Flags.compatchangeForZerojankproxy()
- || CompatChanges.isChangeEnabled(USE_ASYNC_SHOW_HIDE_METHOD);
-
- /**
* If {@code true}, avoid calling the
* {@link com.android.server.inputmethod.InputMethodManagerService InputMethodManagerService}
* by skipping the call to {@link IInputMethodManager#startInputOrWindowGainedFocus}
@@ -614,6 +607,15 @@ public final class InputMethodManager {
@UnsupportedAppUsage
Rect mCursorRect = new Rect();
+ /**
+ * Version-gating is guarded by bug-fix flag.
+ */
+ // Note: this is non-static so that it only gets initialized once CompatChanges has
+ // access to the correct application context.
+ private final boolean mAsyncShowHideMethodEnabled =
+ !Flags.compatchangeForZerojankproxy()
+ || CompatChanges.isChangeEnabled(USE_ASYNC_SHOW_HIDE_METHOD);
+
/** Cached value for {@link #isStylusHandwritingAvailable} for userId. */
@GuardedBy("mH")
private PropertyInvalidatedCache<Integer, Boolean> mStylusHandwritingAvailableCache;
@@ -2419,7 +2421,7 @@ public final class InputMethodManager {
mCurRootView.getLastClickToolType(),
resultReceiver,
reason,
- ASYNC_SHOW_HIDE_METHOD_ENABLED);
+ mAsyncShowHideMethodEnabled);
}
}
}
@@ -2463,7 +2465,7 @@ public final class InputMethodManager {
mCurRootView.getLastClickToolType(),
resultReceiver,
reason,
- ASYNC_SHOW_HIDE_METHOD_ENABLED);
+ mAsyncShowHideMethodEnabled);
}
}
@@ -2572,7 +2574,7 @@ public final class InputMethodManager {
return true;
} else {
return IInputMethodManagerGlobalInvoker.hideSoftInput(mClient, windowToken,
- statsToken, flags, resultReceiver, reason, ASYNC_SHOW_HIDE_METHOD_ENABLED);
+ statsToken, flags, resultReceiver, reason, mAsyncShowHideMethodEnabled);
}
}
}
@@ -2615,7 +2617,7 @@ public final class InputMethodManager {
ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
return IInputMethodManagerGlobalInvoker.hideSoftInput(mClient, view.getWindowToken(),
- statsToken, flags, null, reason, ASYNC_SHOW_HIDE_METHOD_ENABLED);
+ statsToken, flags, null, reason, mAsyncShowHideMethodEnabled);
}
}
@@ -3392,7 +3394,7 @@ public final class InputMethodManager {
servedInputConnection == null ? null
: servedInputConnection.asIRemoteAccessibilityInputConnection(),
view.getContext().getApplicationInfo().targetSdkVersion, targetUserId,
- mImeDispatcher, ASYNC_SHOW_HIDE_METHOD_ENABLED);
+ mImeDispatcher, mAsyncShowHideMethodEnabled);
} else {
res = IInputMethodManagerGlobalInvoker.startInputOrWindowGainedFocus(
startInputReason, mClient, windowGainingFocus, startInputFlags,
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index ccaaf6322f11..086063f3887c 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -299,3 +299,11 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "ensure_keyguard_does_transition_starting"
+ namespace: "windowing_frontend"
+ description: "Ensure that keyguard is the one starting transitions, instead of delegating to Core"
+ bug: "364930619"
+ is_fixed_read_only: true
+}
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedPermissionUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedPermissionUtils.java
index 5651c1ca247f..d4dabf51d4c7 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedPermissionUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedPermissionUtils.java
@@ -16,6 +16,7 @@
package com.android.internal.pm.pkg.component;
+import static com.android.internal.pm.pkg.parsing.ParsingPackageUtils.PARSE_APK_IN_APEX;
import static com.android.internal.pm.pkg.parsing.ParsingUtils.NOT_SET;
import android.annotation.NonNull;
@@ -26,6 +27,7 @@ import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.os.Build;
+import android.permission.flags.Flags;
import android.util.ArrayMap;
import android.util.EventLog;
import android.util.Slog;
@@ -49,7 +51,7 @@ public class ParsedPermissionUtils {
@NonNull
public static ParseResult<ParsedPermission> parsePermission(ParsingPackage pkg, Resources res,
- XmlResourceParser parser, boolean useRoundIcon, ParseInput input)
+ XmlResourceParser parser, boolean useRoundIcon, ParseInput input, int flags)
throws IOException, XmlPullParserException {
String packageName = pkg.getPackageName();
ParsedPermissionImpl permission = new ParsedPermissionImpl();
@@ -77,12 +79,18 @@ public class ParsedPermissionUtils {
if (sa.hasValue(
R.styleable.AndroidManifestPermission_backgroundPermission)) {
- if ("android".equals(packageName)) {
+ final boolean isApkInApex = (flags & PARSE_APK_IN_APEX) != 0;
+ final boolean canUseBackgroundPermissionAttr =
+ "android".equals(packageName) ||
+ (Flags.replaceBodySensorPermissionEnabled() && isApkInApex);
+ if (canUseBackgroundPermissionAttr) {
permission.setBackgroundPermission(sa.getNonResourceString(
- R.styleable.AndroidManifestPermission_backgroundPermission));
+ R.styleable.AndroidManifestPermission_backgroundPermission));
} else {
+ String allowedPackages = "'android'"
+ + (Flags.replaceBodySensorPermissionEnabled() ? " and APK_IN_APEX" : "");
Slog.w(TAG, packageName + " defines a background permission. Only the "
- + "'android' package can do that.");
+ + allowedPackages + " packages can do that.");
}
}
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 44fedb11b043..787006eb214c 100644
--- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
@@ -1090,7 +1090,7 @@ public class ParsingPackageUtils {
case TAG_PERMISSION_GROUP:
return parsePermissionGroup(input, pkg, res, parser);
case TAG_PERMISSION:
- return parsePermission(input, pkg, res, parser);
+ return parsePermission(input, pkg, res, parser, flags);
case TAG_PERMISSION_TREE:
return parsePermissionTree(input, pkg, res, parser);
case TAG_USES_PERMISSION:
@@ -1329,10 +1329,10 @@ public class ParsingPackageUtils {
}
private static ParseResult<ParsingPackage> parsePermission(ParseInput input,
- ParsingPackage pkg, Resources res, XmlResourceParser parser)
+ ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags)
throws XmlPullParserException, IOException {
ParseResult<ParsedPermission> result = ParsedPermissionUtils.parsePermission(
- pkg, res, parser, sUseRoundIcon, input);
+ pkg, res, parser, sUseRoundIcon, input, flags);
if (result.isError()) {
return input.error(result);
}
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index b0e38e256430..cff42fbcfcc5 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -399,13 +399,10 @@ public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProto
return -1;
}
case "enable-text" -> {
- if (mViewerConfigReader != null) {
- mViewerConfigReader.loadViewerConfig(groups, logger);
- }
- return setTextLogging(true, logger, groups);
+ return startLoggingToLogcat(groups, logger);
}
case "disable-text" -> {
- return setTextLogging(false, logger, groups);
+ return stopLoggingToLogcat(groups, logger);
}
default -> {
return unknownCommand(pw);
diff --git a/core/java/com/android/internal/protolog/Utils.java b/core/java/com/android/internal/protolog/Utils.java
index 1e6ba309c046..00ef80ab2bdd 100644
--- a/core/java/com/android/internal/protolog/Utils.java
+++ b/core/java/com/android/internal/protolog/Utils.java
@@ -93,8 +93,7 @@ public class Utils {
os.write(TAG, tag);
break;
default:
- throw new RuntimeException(
- "Unexpected field id " + pis.getFieldNumber());
+ Log.e(LOG_TAG, "Unexpected field id " + pis.getFieldNumber());
}
}
@@ -126,8 +125,7 @@ public class Utils {
os.write(LOCATION, pis.readString(LOCATION));
break;
default:
- throw new RuntimeException(
- "Unexpected field id " + pis.getFieldNumber());
+ Log.e(LOG_TAG, "Unexpected field id " + pis.getFieldNumber());
}
}
diff --git a/core/proto/android/server/vibrator/vibratormanagerservice.proto b/core/proto/android/server/vibrator/vibratormanagerservice.proto
index e7f0560612cc..258832e3e7ff 100644
--- a/core/proto/android/server/vibrator/vibratormanagerservice.proto
+++ b/core/proto/android/server/vibrator/vibratormanagerservice.proto
@@ -157,10 +157,8 @@ message VibratorManagerServiceDumpProto {
option (.android.msg_privacy).dest = DEST_AUTOMATIC;
repeated int32 vibrator_ids = 1;
optional VibrationProto current_vibration = 2;
- optional bool is_vibrating = 3;
optional int32 is_vibrator_controller_registered = 27;
optional VibrationProto current_external_vibration = 4;
- optional bool vibrator_under_external_control = 5;
optional bool low_power_mode = 6;
optional bool vibrate_on = 24;
reserved 25; // prev keyboard_vibration_on
@@ -183,4 +181,6 @@ message VibratorManagerServiceDumpProto {
repeated VibrationProto previous_vibrations = 16;
repeated VibrationParamProto previous_vibration_params = 28;
reserved 17; // prev previous_external_vibrations
+ reserved 3; // prev is_vibrating, check current_vibration instead
+ reserved 5; // prev vibrator_under_external_control, check current_external_vibration instead
} \ No newline at end of file
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index 93fdf3dc5207..9b5a825b9e38 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -2408,9 +2408,9 @@
<string name="keyboard_layout_notification_more_than_three_selected_message" msgid="1581834181578206937">"За клавиатурната подредба са зададени <xliff:g id="LAYOUT_1">%1$s</xliff:g>, <xliff:g id="LAYOUT_2">%2$s</xliff:g> и <xliff:g id="LAYOUT_3">%3$s</xliff:g>… Докоснете за промяна."</string>
<string name="keyboard_layout_notification_multiple_selected_title" msgid="5242444914367024499">"Физическите клавиатури са конфигурирани"</string>
<string name="keyboard_layout_notification_multiple_selected_message" msgid="6576533454124419202">"Докоснете за преглед на клавиатурите"</string>
- <string name="profile_label_private" msgid="6463418670715290696">"Частни"</string>
+ <string name="profile_label_private" msgid="6463418670715290696">"Частен"</string>
<string name="profile_label_clone" msgid="769106052210954285">"Клониране"</string>
- <string name="profile_label_work" msgid="3495359133038584618">"Служебни"</string>
+ <string name="profile_label_work" msgid="3495359133038584618">"Служебен"</string>
<string name="profile_label_work_2" msgid="4691533661598632135">"Служебни 2"</string>
<string name="profile_label_work_3" msgid="4834572253956798917">"Служебни 3"</string>
<string name="profile_label_test" msgid="9168641926186071947">"Тестване"</string>
diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml
index 50f2221d8fde..9d5ecdcc1933 100644
--- a/core/res/res/values-bs/strings.xml
+++ b/core/res/res/values-bs/strings.xml
@@ -2154,7 +2154,7 @@
<string name="nas_upgrade_notification_enable_action" msgid="3046406808378726874">"Uredu"</string>
<string name="nas_upgrade_notification_disable_action" msgid="3794833210043497982">"Isključi"</string>
<string name="nas_upgrade_notification_learn_more_action" msgid="7011130656195423947">"Saznajte više"</string>
- <string name="nas_upgrade_notification_learn_more_content" msgid="3735480566983530650">"Poboljšana obavještenja su zamijenila Prilagodljiva obavještenja Androida u verziji Android 12. Ova funkcija prikazuje predložene radnje i odgovore te organizira vaša obavještenja.\n\nPoboljšana obavještenja mogu pristupiti sadržaju obavještenja, uključujući lične informacije kao što su imena kontakata i poruke. Ova funkcija također može odbacivati obavještenja ili reagirati na njih, npr. može odgovarati na telefonske pozive i kontrolirati funkciju Ne ometaj."</string>
+ <string name="nas_upgrade_notification_learn_more_content" msgid="3735480566983530650">"Poboljšana obavještenja su zamijenila prilagodljiva obavještenja Androida u verziji Android 12. Ova funkcija prikazuje predložene radnje i odgovore te organizira vaša obavještenja.\n\nPoboljšana obavještenja mogu pristupiti sadržaju obavještenja, uključujući lične informacije kao što su imena kontakata i poruke. Ova funkcija također može odbacivati obavještenja ili reagirati na njih, npr. može odgovarati na telefonske pozive i kontrolirati funkciju Ne ometaj."</string>
<string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Obavještenje za informacije Rutinskog načina"</string>
<string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Ušteda baterije je uključena"</string>
<string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Smanjena je potrošnja baterije da se produži vijek trajanja baterije"</string>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index e37106d3b27c..6933a16dd97e 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -22,7 +22,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="byteShort" msgid="202579285008794431">"بایت"</string>
<string name="fileSizeSuffix" msgid="4233671691980131257">"<xliff:g id="NUMBER">%1$s</xliff:g> ‏<xliff:g id="UNIT">%2$s</xliff:g>"</string>
- <string name="untitled" msgid="3381766946944136678">"‏&lt;بدون عنوان&gt;"</string>
+ <string name="untitled" msgid="3381766946944136678">"‏&lt;بی‌عنوان&gt;"</string>
<string name="emptyPhoneNumber" msgid="5812172618020360048">"(بدون شماره تلفن)"</string>
<string name="unknownName" msgid="7078697621109055330">"نامشخص"</string>
<string name="defaultVoiceMailAlphaTag" msgid="2190754495304236490">"پست صوتی"</string>
@@ -196,7 +196,7 @@
<string name="ssl_ca_cert_noti_by_unknown" msgid="4961102218216815242">"توسط یک شخص ثالث ناشناس"</string>
<string name="ssl_ca_cert_noti_by_administrator" msgid="4564941950768783879">"توسط سرپرست نمایه کاری شما"</string>
<string name="ssl_ca_cert_noti_managed" msgid="217337232273211674">"توسط <xliff:g id="MANAGING_DOMAIN">%s</xliff:g>"</string>
- <string name="work_profile_deleted" msgid="5891181538182009328">"نمایه کار حذف شد"</string>
+ <string name="work_profile_deleted" msgid="5891181538182009328">"نمایه کاری حذف شد"</string>
<string name="work_profile_deleted_details" msgid="3773706828364418016">"برنامه سرپرست نمایه کاری یا وجود ندارد یا خراب است. در نتیجه، نمایه کاری شما و داده‌های مرتبط با آن حذف شده است. برای دریافت راهنمایی با سرپرست سیستم تماس بگیرید."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"نمایه کاری شما دیگر در این دستگاه دردسترس نیست"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"تلاش‌های بسیار زیادی برای وارد کردن گذرواژه انجام شده است"</string>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index 672e50805967..18c7902511a0 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -2010,7 +2010,7 @@
<string name="set_up_screen_lock_title" msgid="8346083801616474030">"Näytön lukituksen asettaminen"</string>
<string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"Aseta näytön lukitus"</string>
<string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"Edellyttää näytön lukitusta"</string>
- <string name="private_space_set_up_screen_lock_for_reset" msgid="7817091386408432097">"Jos haluat poistaa yksityisen tilan, aseta laitteelle näytön lukitus"</string>
+ <string name="private_space_set_up_screen_lock_for_reset" msgid="7817091386408432097">"Jos haluat poistaa yksityisen tilan, lisää laitteelle näytön lukitus"</string>
<string name="app_blocked_title" msgid="7353262160455028160">"Sovellus ei ole käytettävissä"</string>
<string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ei ole nyt käytettävissä."</string>
<string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> ei käytettävissä"</string>
diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml
index 126d55eec3e2..419dd17d233c 100644
--- a/core/res/res/values-kn/strings.xml
+++ b/core/res/res/values-kn/strings.xml
@@ -197,7 +197,7 @@
<string name="ssl_ca_cert_noti_by_administrator" msgid="4564941950768783879">"ನಿಮ್ಮ ಉದ್ಯೋಗ ಪ್ರೊಫೈಲ್‌ ನಿರ್ವಾಹಕರಿಂದ"</string>
<string name="ssl_ca_cert_noti_managed" msgid="217337232273211674">"<xliff:g id="MANAGING_DOMAIN">%s</xliff:g> ಪ್ರಕಾರ"</string>
<string name="work_profile_deleted" msgid="5891181538182009328">"ಕೆಲಸದ ಪ್ರೊಫೈಲ್ ಅನ್ನು ಅಳಿಸಲಾಗಿದೆ"</string>
- <string name="work_profile_deleted_details" msgid="3773706828364418016">"ಉದ್ಯೋಗ ಪ್ರೊಫೈಲ್‌ ನಿರ್ವಾಹಕ ಅಪ್ಲಿಕೇಶನ್ ಕಳೆದು ಹೋಗಿದೆ ಅಥವಾ ಹಾಳಾಗಿದೆ. ಇದರ ಪರಿಣಾಮವಾಗಿ ನಿಮ್ಮ ಉದ್ಯೋಗ ಪ್ರೊಫೈಲ್‌ ಮತ್ತು ಅದಕ್ಕೆ ಸಂಬಂಧಿಸಿದ ಡೇಟಾವನ್ನು ಅಳಿಸಲಾಗಿದೆ. ಸಹಾಯಕ್ಕಾಗಿ ನಿಮ್ಮ ನಿರ್ವಾಹಕರನ್ನು ಸಂಪರ್ಕಿಸಿ."</string>
+ <string name="work_profile_deleted_details" msgid="3773706828364418016">"ಉದ್ಯೋಗ ಪ್ರೊಫೈಲ್‌ ನಿರ್ವಾಹಕ ಆ್ಯಪ್ ಕಳೆದು ಹೋಗಿದೆ ಅಥವಾ ಹಾಳಾಗಿದೆ. ಇದರ ಪರಿಣಾಮವಾಗಿ ನಿಮ್ಮ ಉದ್ಯೋಗ ಪ್ರೊಫೈಲ್‌ ಮತ್ತು ಅದಕ್ಕೆ ಸಂಬಂಧಿಸಿದ ಡೇಟಾವನ್ನು ಅಳಿಸಲಾಗಿದೆ. ಸಹಾಯಕ್ಕಾಗಿ ನಿಮ್ಮ ನಿರ್ವಾಹಕರನ್ನು ಸಂಪರ್ಕಿಸಿ."</string>
<string name="work_profile_deleted_description_dpm_wipe" msgid="2477244968924647232">"ನಿಮ್ಮ ಉದ್ಯೋಗ ಪ್ರೊಫೈಲ್‌ ಈ ಸಾಧನದಲ್ಲಿ ಈಗ ಲಭ್ಯವಿಲ್ಲ"</string>
<string name="work_profile_deleted_reason_maximum_password_failure" msgid="1080323158315663167">"ಹಲವಾರು ಪಾಸ್‌ವರ್ಡ್ ಪ್ರಯತ್ನಗಳು"</string>
<string name="device_ownership_relinquished" msgid="4080886992183195724">"ವೈಯಕ್ತಿಕ ಬಳಕೆಗಾಗಿ ನಿರ್ವಾಹಕರು ತೊರೆದ ಸಾಧನ"</string>
@@ -217,7 +217,7 @@
<string name="device_policy_manager_service" msgid="5085762851388850332">"ಸಾಧನ ನೀತಿ ನಿರ್ವಾಹಕ ಸೇವೆ"</string>
<string name="music_recognition_manager_service" msgid="7481956037950276359">"ಸಂಗೀತ ಗುರುತಿಸುವಿಕೆ ನಿರ್ವಾಹಕ ಸೇವೆ"</string>
<string name="factory_reset_warning" msgid="6858705527798047809">"ನಿಮ್ಮ ಸಾಧನವನ್ನು ಅಳಿಸಲಾಗುತ್ತದೆ"</string>
- <string name="factory_reset_message" msgid="2657049595153992213">"ನಿರ್ವಹಣೆ ಅಪ್ಲಿಕೇಶನ್ ಬಳಸಲು ಸಾಧ್ಯವಿಲ್ಲ. ನಿಮ್ಮ ಸಾಧನವನ್ನು ಇದೀಗ ಅಳಿಸಲಾಗುತ್ತದೆ.\n\nನಿಮ್ಮಲ್ಲಿ ಪ್ರಶ್ನೆಗಳಿದ್ದರೆ, ನಿಮ್ಮ ಸಂಸ್ಥೆಯ ನಿರ್ವಾಹಕರನ್ನು ಸಂಪರ್ಕಿಸಿ."</string>
+ <string name="factory_reset_message" msgid="2657049595153992213">"ನಿರ್ವಹಣೆ ಆ್ಯಪ್ ಬಳಸಲು ಸಾಧ್ಯವಿಲ್ಲ. ನಿಮ್ಮ ಸಾಧನವನ್ನು ಇದೀಗ ಅಳಿಸಲಾಗುತ್ತದೆ.\n\nನಿಮ್ಮಲ್ಲಿ ಪ್ರಶ್ನೆಗಳಿದ್ದರೆ, ನಿಮ್ಮ ಸಂಸ್ಥೆಯ ನಿರ್ವಾಹಕರನ್ನು ಸಂಪರ್ಕಿಸಿ."</string>
<string name="printing_disabled_by" msgid="3517499806528864633">"<xliff:g id="OWNER_APP">%s</xliff:g> ಮೂಲಕ ಪ್ರಿಂಟಿಂಗ್ ಅನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ."</string>
<string name="personal_apps_suspension_title" msgid="7561416677884286600">"ಉದ್ಯೋಗ ಪ್ರೊಫೈಲ್ ಸಕ್ರಿಯಗೊಳಿಸಿ"</string>
<string name="personal_apps_suspension_text" msgid="6115455688932935597">"ನಿಮ್ಮ ಉದ್ಯೋಗದ ಪ್ರೊಫೈಲ್ ಅನ್ನು ಸಕ್ರಿಯಗೊಳಿಸುವವರೆಗೆ ನಿಮ್ಮ ವೈಯಕ್ತಿಕ ಆ್ಯಪ್‌ಗಳನ್ನು ನಿರ್ಬಂಧಿಸಲಾಗುತ್ತದೆ"</string>
@@ -366,7 +366,7 @@
<string name="permlab_statusBar" msgid="8798267849526214017">"ಸ್ಥಿತಿ ಪಟ್ಟಿಯನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಿ ಇಲ್ಲವೇ ಮಾರ್ಪಡಿಸಿ"</string>
<string name="permdesc_statusBar" msgid="5809162768651019642">"ಸ್ಥಿತಿ ಪಟ್ಟಿಯನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲು ಅಥವಾ ಸೇರಿಸಲು ಮತ್ತು ಸಿಸ್ಟಂ ಐಕಾನ್‌ಗಳನ್ನು ತೆಗೆದುಹಾಕಲು ಅಪ್ಲಿಕೇಶನ್‌ಗೆ ಅವಕಾಶ ನೀಡುತ್ತದೆ."</string>
<string name="permlab_statusBarService" msgid="2523421018081437981">"ಸ್ಥಿತಿ ಪಟ್ಟಿಯಾಗಿರಲು"</string>
- <string name="permdesc_statusBarService" msgid="6652917399085712557">"ಅಪ್ಲಿಕೇಶನ್ ಅನ್ನು ಸ್ಥಿತಿ ಪಟ್ಟಿಯಾಗಿ ಅನುಮತಿಸುತ್ತದೆ."</string>
+ <string name="permdesc_statusBarService" msgid="6652917399085712557">"ಆ್ಯಪ್‌ ಅನ್ನು ಸ್ಥಿತಿ ಪಟ್ಟಿಯಾಗಿ ಅನುಮತಿಸುತ್ತದೆ."</string>
<string name="permlab_expandStatusBar" msgid="1184232794782141698">"ಸ್ಥಿತಿ ಪಟ್ಟಿಯನ್ನು ವಿಸ್ತರಿಸಿ/ಸಂಕುಚಿಸಿ"</string>
<string name="permdesc_expandStatusBar" msgid="7180756900448498536">"ಸ್ಥಿತಿ ಪಟ್ಟಿಯನ್ನು ವಿಸ್ತರಿಸಲು ಅಥವಾ ಸಂಕುಚಿಸಲು ಅಪ್ಲಿಕೇಶನ್‌‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ."</string>
<string name="permlab_fullScreenIntent" msgid="4310888199502509104">"ಲಾಕ್ ಮಾಡಲಾದ ಸಾಧನದಲ್ಲಿ ಅಧಿಸೂಚನೆಗಳನ್ನು ಪೂರ್ಣ-ಸ್ಕ್ರೀನ್ ಚಟುವಟಿಕೆ ರೀತಿಯಲ್ಲಿ ಡಿಸ್‌ಪ್ಲೇ ಮಾಡಿ"</string>
@@ -378,11 +378,11 @@
<string name="permlab_processOutgoingCalls" msgid="4075056020714266558">"ಹೊರಹೋಗುವ ಕರೆಗಳ ಮಾರ್ಗ ಬದಲಿಸಿ"</string>
<string name="permdesc_processOutgoingCalls" msgid="7833149750590606334">"ಬೇರೊಂದು ಸಂಖ್ಯೆಗೆ ಕರೆಯನ್ನು ಮರುನಿರ್ದೇಶಿಸಲು ಆಯ್ಕೆಯ ಜೊತೆಗೆ ಹೊರ ಹೋಗುವ ಕರೆಯ ಸಮಯದಲ್ಲಿ ಡಯಲ್‌ ಮಾಡಿದ ಸಂಖ್ಯೆಯನ್ನು ನೋಡಲು ಅಪ್ಲಿಕೇಶನ್‌‌ಗೆ ಅವಕಾಶ ನೀಡುತ್ತದೆ."</string>
<string name="permlab_answerPhoneCalls" msgid="4131324833663725855">"ಫೋನ್ ಕರೆಗಳಿಗೆ ಉತ್ತರಿಸಿ"</string>
- <string name="permdesc_answerPhoneCalls" msgid="894386681983116838">"ಒಳಬರುವ ಫೋನ್ ಕರೆಗೆ ಉತ್ತರಿಸಲು ಅಪ್ಲಿಕೇಶನ್ ಅನ್ನು ಅನುಮತಿಸುತ್ತದೆ."</string>
+ <string name="permdesc_answerPhoneCalls" msgid="894386681983116838">"ಒಳಬರುವ ಫೋನ್ ಕರೆಗೆ ಉತ್ತರಿಸಲು ಆ್ಯಪ್ ಅನ್ನು ಅನುಮತಿಸುತ್ತದೆ."</string>
<string name="permlab_receiveSms" msgid="505961632050451881">"ಪಠ್ಯ ಸಂದೇಶಗಳನ್ನು ಸ್ವೀಕರಿಸಿ (SMS)"</string>
- <string name="permdesc_receiveSms" msgid="1797345626687832285">"SMS ಸಂದೇಶಗಳನ್ನು ಸ್ವೀಕರಿಸಲು ಮತ್ತು ಪ್ರಕ್ರಿಯೆಗೊಳಿಸಲು ಅಪ್ಲಿಕೇಶನ್‍‍ಗೆ ಅವಕಾಶ ಮಾಡಿಕೊಡುತ್ತದೆ. ಇದರರ್ಥ, ನಿಮ್ಮ ಸಾಧನಕ್ಕೆ ಕಳುಹಿಸಲಾಗಿರುವ ಸಂದೇಶಗಳನ್ನು ನಿಮಗೆ ತೋರಿಸದೆಯೇ, ಅಪ್ಲಿಕೇಶನ್ ಅವುಗಳನ್ನು ಮಾನಿಟರ್ ಮಾಡಬಹುದು ಅಥವಾ ಅಳಿಸಬಹುದು."</string>
+ <string name="permdesc_receiveSms" msgid="1797345626687832285">"SMS ಸಂದೇಶಗಳನ್ನು ಸ್ವೀಕರಿಸಲು ಮತ್ತು ಪ್ರಕ್ರಿಯೆಗೊಳಿಸಲು ಆ್ಯಪ್‌ಗೆ ಅವಕಾಶ ಮಾಡಿಕೊಡುತ್ತದೆ. ಇದರರ್ಥ, ನಿಮ್ಮ ಸಾಧನಕ್ಕೆ ಕಳುಹಿಸಲಾಗಿರುವ ಸಂದೇಶಗಳನ್ನು ನಿಮಗೆ ತೋರಿಸದೆಯೇ, ಆ್ಯಪ್‌ ಅವುಗಳನ್ನು ಮಾನಿಟರ್ ಮಾಡಬಹುದು ಅಥವಾ ಅಳಿಸಬಹುದು."</string>
<string name="permlab_receiveMms" msgid="4000650116674380275">"ಪಠ್ಯ ಸಂದೇಶಗಳನ್ನು ಸ್ವೀಕರಿಸಿ (MMS)"</string>
- <string name="permdesc_receiveMms" msgid="958102423732219710">"MMS ಸಂದೇಶಗಳನ್ನು ಸ್ವೀಕರಿಸಲು ಮತ್ತು ಪ್ರಕ್ರಿಯೆಗೊಳಿಸಲು ಅಪ್ಲಿಕೇಶನ್‍‍ಗೆ ಅವಕಾಶ ಮಾಡಿಕೊಡುತ್ತದೆ. ಇದರರ್ಥ, ನಿಮ್ಮ ಸಾಧನಕ್ಕೆ ಕಳುಹಿಸಲಾಗಿರುವ ಸಂದೇಶಗಳನ್ನು ನಿಮಗೆ ತೋರಿಸದೆಯೇ, ಅಪ್ಲಿಕೇಶನ್ ಅವುಗಳನ್ನು ಮಾನಿಟರ್ ಮಾಡಬಹುದು ಅಥವಾ ಅಳಿಸಬಹುದು."</string>
+ <string name="permdesc_receiveMms" msgid="958102423732219710">"MMS ಸಂದೇಶಗಳನ್ನು ಸ್ವೀಕರಿಸಲು ಮತ್ತು ಪ್ರಕ್ರಿಯೆಗೊಳಿಸಲು ಆ್ಯಪ್‌ಗೆ ಅವಕಾಶ ಮಾಡಿಕೊಡುತ್ತದೆ. ಇದರರ್ಥ, ನಿಮ್ಮ ಸಾಧನಕ್ಕೆ ಕಳುಹಿಸಲಾಗಿರುವ ಸಂದೇಶಗಳನ್ನು ನಿಮಗೆ ತೋರಿಸದೆಯೇ, ಆ್ಯಪ್‌ ಅವುಗಳನ್ನು ಮಾನಿಟರ್ ಮಾಡಬಹುದು ಅಥವಾ ಅಳಿಸಬಹುದು."</string>
<string name="permlab_bindCellBroadcastService" msgid="586746677002040651">"ಸೆಲ್ ಪ್ರಸಾರ ಸಂದೇಶಗಳನ್ನು ಫಾರ್ವರ್ಡ್ ಮಾಡಿ"</string>
<string name="permdesc_bindCellBroadcastService" msgid="6540910200973641606">"ಸೆಲ್ ಪ್ರಸಾರವು ಸಂದೇಶಗಳನ್ನು ಸ್ವೀಕರಿಸಿದ ರೀತಿಯಲ್ಲಿಯೇ ಫಾರ್ವರ್ಡ್ ಮಾಡಲು, ಸೆಲ್ ಪ್ರಸಾರ ಮಾಡ್ಯುಲ್‌ ಅನ್ನು ಪ್ರತಿಬಂಧಿಸಲು ಆ್ಯಪ್‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ. ಕೆಲವು ಸ್ಥಳಗಳಲ್ಲಿ ತುರ್ತು ಸ್ಥಿತಿಗಳ ಕುರಿತು ನಿಮಗೆ ಎಚ್ಚರಿಸಲು ಸೆಲ್ ಪ್ರಸಾರದ ಎಚ್ಚರಿಕೆಗಳನ್ನು ಕಳುಹಿಸಲಾಗುತ್ತದೆ. ತುರ್ತು ಸೆಲ್‌ ಪ್ರಸಾರವನ್ನು ಸ್ವೀಕರಿಸಿದಾಗ ನಿಮ್ಮ ಸಾಧನದ ಕಾರ್ಯಾಚರಣೆ ಅಥವಾ ಕಾರ್ಯಕ್ಷಮತೆಗೆ ದುರುದ್ದೇಶಪೂರಿತ ಆ್ಯಪ್‌ಗಳು ಹಸ್ತಕ್ಷೇಪ ಮಾಡಬಹುದು."</string>
<string name="permlab_manageOngoingCalls" msgid="281244770664231782">"ಚಾಲ್ತಿಯಲ್ಲಿರುವ ಕರೆಗಳನ್ನು ನಿರ್ವಹಿಸಿ"</string>
@@ -396,9 +396,9 @@
<string name="permlab_sendSms" msgid="7757368721742014252">"SMS ಸಂದೇಶಗಳನ್ನು ಕಳುಹಿಸಲು ಮತ್ತು ನಿರ್ವಹಿಸಲು"</string>
<string name="permdesc_sendSms" msgid="6757089798435130769">"SMS ಸಂದೇಶಗಳನ್ನು ಕಳುಹಿಸಲು ಅಪ್ಲಿಕೇಶನ್‍‍ಗೆ ಅವಕಾಶ ನೀಡುತ್ತದೆ. ಇದು ಅನಿರೀಕ್ಷಿತ ವೆಚ್ಚಗಳಿಗೆ ಕಾರಣವಾಗಬಹುದು. ದುರುದ್ದೇಶಪೂರಿತ ಅಪ್ಲಿಕೇಶನ್‍‍ಗಳು ನಿಮ್ಮ ದೃಢೀಕರಣವಿಲ್ಲದೆಯೇ ಸಂದೇಶಗಳನ್ನು ಕಳುಹಿಸುವ ಮೂಲಕ ನಿಮ್ಮ ಹಣವನ್ನು ವ್ಯಯಿಸಬಹುದು."</string>
<string name="permlab_readSms" msgid="5164176626258800297">"ನಿಮ್ಮ ಪಠ್ಯ ಸಂದೇಶಗಳನ್ನು ಓದಿ (SMS ಅಥವಾ MMS)"</string>
- <string name="permdesc_readSms" product="tablet" msgid="7912990447198112829">"ಈ ಅಪ್ಲಿಕೇಶನ್ ನಿಮ್ಮ ಟ್ಯಾಬ್ಲೆಟ್‌ನಲ್ಲಿ ಸಂಗ್ರಹವಾಗಿರುವ ಎಲ್ಲಾ ಎಸ್ಎಂಎಸ್ (ಪಠ್ಯ) ಸಂದೇಶಗಳನ್ನು ಓದಬಹುದು."</string>
- <string name="permdesc_readSms" product="tv" msgid="3054753345758011986">"ಈ ಅಪ್ಲಿಕೇಶನ್ ನಿಮ್ಮ Android TV ಸಾಧನದಲ್ಲಿ ಸಂಗ್ರಹವಾಗಿರುವ ಎಲ್ಲಾ SMS (ಪಠ್ಯ) ಸಂದೇಶಗಳನ್ನು ಓದಬಹುದು."</string>
- <string name="permdesc_readSms" product="default" msgid="774753371111699782">"ಈ ಅಪ್ಲಿಕೇಶನ್ ನಿಮ್ಮ ಫೋನ್‌ನಲ್ಲಿ ಸಂಗ್ರಹವಾಗಿರುವ ಎಲ್ಲಾ ಎಸ್ಎಂಎಸ್ (ಪಠ್ಯ) ಸಂದೇಶಗಳನ್ನು ಓದಬಹುದು."</string>
+ <string name="permdesc_readSms" product="tablet" msgid="7912990447198112829">"ಈ ಆ್ಯಪ್‌ ನಿಮ್ಮ ಟ್ಯಾಬ್ಲೆಟ್‌ನಲ್ಲಿ ಸಂಗ್ರಹವಾಗಿರುವ ಎಲ್ಲಾ ಎಸ್ಎಂಎಸ್ (ಪಠ್ಯ) ಸಂದೇಶಗಳನ್ನು ಓದಬಹುದು."</string>
+ <string name="permdesc_readSms" product="tv" msgid="3054753345758011986">"ಈ ಆ್ಯಪ್ ನಿಮ್ಮ Android TV ಸಾಧನದಲ್ಲಿ ಸಂಗ್ರಹವಾಗಿರುವ ಎಲ್ಲಾ SMS (ಪಠ್ಯ) ಸಂದೇಶಗಳನ್ನು ಓದಬಹುದು."</string>
+ <string name="permdesc_readSms" product="default" msgid="774753371111699782">"ಈ ಆ್ಯಪ್‌ ನಿಮ್ಮ ಫೋನ್‌ನಲ್ಲಿ ಸಂಗ್ರಹವಾಗಿರುವ ಎಲ್ಲಾ ಎಸ್ಎಂಎಸ್ (ಪಠ್ಯ) ಸಂದೇಶಗಳನ್ನು ಓದಬಹುದು."</string>
<string name="permlab_receiveWapPush" msgid="4223747702856929056">"ಪಠ್ಯ ಸಂದೇಶಗಳನ್ನು ಸ್ವೀಕರಿಸಿ (WAP)"</string>
<string name="permdesc_receiveWapPush" msgid="1638677888301778457">"WAP ಸಂದೇಶಗಳನ್ನು ಸ್ವೀಕರಿಸಲು ಮತ್ತು ಪ್ರಕ್ರಿಯೆಗೊಳಿಸಲು ಅಪ್ಲಿಕೇಶನ್‍‍ಗೆ ಅವಕಾಶ ಮಾಡಿಕೊಡುತ್ತದೆ. ಈ ಅನುಮತಿಯು, ನಿಮಗೆ ಕಳುಹಿಸಲಾಗಿರುವ ಸಂದೇಶಗಳನ್ನು ನಿಮಗೆ ತೋರಿಸದೆಯೇ, ಅವುಗಳನ್ನು ಮಾನಿಟರ್ ಮಾಡುವ ಅಥವಾ ಅಳಿಸುವ ಸಾಮರ್ಥ್ಯವನ್ನು ಒಳಗೊಂಡಿರುತ್ತದೆ."</string>
<string name="permlab_getTasks" msgid="7460048811831750262">"ರನ್‌ ಆಗುತ್ತಿರುವ ಅಪ್ಲಿಕೇಶನ್‌ಗಳನ್ನು ಹಿಂಪಡೆಯಿರಿ"</string>
@@ -406,24 +406,24 @@
<string name="permlab_manageProfileAndDeviceOwners" msgid="639849495253987493">"ಪ್ರೊಫೈಲ್ ಮತ್ತು ಸಾಧನ ಮಾಲೀಕರನ್ನು ನಿರ್ವಹಿಸಿ"</string>
<string name="permdesc_manageProfileAndDeviceOwners" msgid="7304240671781989283">"ಪ್ರೊಫೈಲ್ ಮಾಲೀಕರು ಮತ್ತು ಸಾಧನ ಮಾಲೀಕರನ್ನು ಹೊಂದಿಸಲು ಅಪ್ಲಿಕೇಶನ್‌ಗಳನ್ನು ಅನುಮತಿಸುತ್ತದೆ."</string>
<string name="permlab_reorderTasks" msgid="7598562301992923804">"ರನ್‌ ಆಗುತ್ತಿರುವ ಅಪ್ಲಿಕೇಶನ್‌ಗಳನ್ನು ಮರುಕ್ರಮಗೊಳಿಸಿ"</string>
- <string name="permdesc_reorderTasks" msgid="8796089937352344183">"ಮುನ್ನೆಲೆ ಮತ್ತು ಹಿನ್ನಲೆಗೆ ಕಾರ್ಯಗಳನ್ನು ಸರಿಸಲು ಅಪ್ಲಿಕೇಶನ್‍‍ಗೆ ಅನುಮತಿಸುತ್ತದೆ. ನಿಮ್ಮ ಇನ್‍‍ಪುಟ್ ಇಲ್ಲದೆಯೇ, ಅಪ್ಲಿಕೇಶನ್ ಈ ಕಾರ್ಯವನ್ನು ಮಾಡಬಹುದು."</string>
+ <string name="permdesc_reorderTasks" msgid="8796089937352344183">"ಮುನ್ನೆಲೆ ಮತ್ತು ಹಿನ್ನಲೆಗೆ ಕಾರ್ಯಗಳನ್ನು ಸರಿಸಲು ಆ್ಯಪ್‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ. ನಿಮ್ಮ ಇನ್‍‍ಪುಟ್ ಇಲ್ಲದೆಯೇ, ಆ್ಯಪ್‌ ಈ ಕಾರ್ಯವನ್ನು ಮಾಡಬಹುದು."</string>
<string name="permlab_enableCarMode" msgid="893019409519325311">"ಕಾರು ಮೋಡ್ ಸಕ್ರಿಯಗೊಳಿಸಿ"</string>
<string name="permdesc_enableCarMode" msgid="56419168820473508">"ಕಾರು‌ ಮೋಡ್‌ ಸಕ್ರಿಯಗೊಳಿಸಲು ಅಪ್ಲಿಕೇಶನ್‌‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ."</string>
<string name="permlab_killBackgroundProcesses" msgid="6559320515561928348">"ಇತರೆ ಅಪ್ಲಿಕೇಶನ್‍ಗಳನ್ನು ಮುಚ್ಚಿ"</string>
<string name="permdesc_killBackgroundProcesses" msgid="2357013583055434685">"ಇತರ ಅಪ್ಲಿಕೇಶನ್‍‍ಗಳ ಹಿನ್ನೆಲೆ ಪ್ರಕ್ರಿಯೆಗಳನ್ನು ಅಂತ್ಯಗೊಳಿಸಲು ಅಪ್ಲಿಕೇಶನ್‍‍ಗೆ ಅವಕಾಶ ಮಾಡಿಕೊಡುತ್ತದೆ. ಇದು ಇತರ ಅಪ್ಲಿಕೇಶನ್‍‍ಗಳ ಚಾಲನೆಯನ್ನು ನಿಲ್ಲಿಸುವುದಕ್ಕೆ ಕಾರಣವಾಗಬಹುದು."</string>
- <string name="permlab_systemAlertWindow" msgid="5757218350944719065">"ಈ ಅಪ್ಲಿಕೇಶನ್ ಇತರ ಅಪ್ಲಿಕೇಶನ್‌ಗಳ ಮೇಲೆ ಕಾಣಿಸಿಕೊಳ್ಳಬಹುದು"</string>
- <string name="permdesc_systemAlertWindow" msgid="1145660714855738308">"ಈ ಅಪ್ಲಿಕೇಶನ್ ಇತರ ಅಪ್ಲಿಕೇಶನ್‌ಗಳ ಅಥವಾ ಪರದೆಯ ಇತರೆ ಭಾಗಗಳ ಮೇಲೆ ಕಾಣಿಸಿಕೊಳ್ಳಬಹುದು. ಇದು ಸಾಮಾನ್ಯ ಅಪ್ಲಿಕೇಶನ್ ಬಳಕೆಯ ಮೂಲಕ ಹಸ್ತಕ್ಷೇಪ ಮಾಡಬಹುದು ಮತ್ತು ಇತರೆ ಅಪ್ಲಿಕೇಶನ್ ಗೋಚರಿಸುವ ರೀತಿಯಲ್ಲಿ ಬದಲಾಯಿಸಬಹುದು."</string>
+ <string name="permlab_systemAlertWindow" msgid="5757218350944719065">"ಈ ಆ್ಯಪ್‌ ಇತರ ಆ್ಯಪ್‌ಗಳ ಮೇಲೆ ಕಾಣಿಸಿಕೊಳ್ಳಬಹುದು"</string>
+ <string name="permdesc_systemAlertWindow" msgid="1145660714855738308">"ಈ ಆ್ಯಪ್ ಇತರ ಆ್ಯಪ್‌ಗಳ ಅಥವಾ ಪರದೆಯ ಇತರೆ ಭಾಗಗಳ ಮೇಲೆ ಕಾಣಿಸಿಕೊಳ್ಳಬಹುದು. ಇದು ಸಾಮಾನ್ಯ ಆ್ಯಪ್ ಬಳಕೆಯ ಮೂಲಕ ಹಸ್ತಕ್ಷೇಪ ಮಾಡಬಹುದು ಮತ್ತು ಇತರೆ ಆ್ಯಪ್‌ಗಳು ಗೋಚರಿಸುವ ರೀತಿಯಲ್ಲಿ ಬದಲಾಯಿಸಬಹುದು."</string>
<string name="permlab_hideOverlayWindows" msgid="6382697828482271802">"ಇತರ ಆ್ಯಪ್‌ಗಳ ಓವರ್‌ಲೇಗಳನ್ನು ಮರೆಮಾಡಿ"</string>
<string name="permdesc_hideOverlayWindows" msgid="5660242821651958225">"ಇತರ ಆ್ಯಪ್‌ಗಳಿಂದ ರಚಿಸಲ್ಪಡುವ ಓವರ್‌ಲೇಗಳು ಈ ಆ್ಯಪ್‌ನಲ್ಲಿ ಕಾಣಿಸಿಕೊಳ್ಳದಂತೆ ಮರೆಮಾಡಲು ಸಿಸ್ಟಮ್‌ಗೆ ಈ ಆ್ಯಪ್ ವಿನಂತಿಸಬಹುದು."</string>
<string name="permlab_runInBackground" msgid="541863968571682785">"ಹಿನ್ನಲೆಯಲ್ಲಿ ರನ್ ಮಾಡಿ"</string>
- <string name="permdesc_runInBackground" msgid="4344539472115495141">"ಈ ಅಪ್ಲಿಕೇಶನ್ ಹಿನ್ನಲೆಯಲ್ಲಿ ರನ್ ಆಗಬಹುದು. ಇದು ಬ್ಯಾಟರಿಯನ್ನು ವೇಗವಾಗಿ ಬರಿದಾಗಿಸಬಹುದು."</string>
+ <string name="permdesc_runInBackground" msgid="4344539472115495141">"ಈ ಆ್ಯಪ್‌ ಹಿನ್ನಲೆಯಲ್ಲಿ ರನ್ ಆಗಬಹುದು. ಇದು ಬ್ಯಾಟರಿಯನ್ನು ವೇಗವಾಗಿ ಬರಿದಾಗಿಸಬಹುದು."</string>
<string name="permlab_useDataInBackground" msgid="783415807623038947">"ಹಿನ್ನಲೆಯಲ್ಲಿ ಡೇಟಾ ಬಳಕೆ ಮಾಡಿ"</string>
- <string name="permdesc_useDataInBackground" msgid="1230753883865891987">"ಈ ಅಪ್ಲಿಕೇಶನ್ ಹಿನ್ನಲೆಯಲ್ಲಿ ಡೇಟಾವನ್ನು ಬಳಸಬಹುದು. ಇದರಿಂದ ಡೇಟಾ ಬಳಕೆ ಹೆಚ್ಚಾಗಬಹುದು."</string>
+ <string name="permdesc_useDataInBackground" msgid="1230753883865891987">"ಈ ಆ್ಯಪ್‌ ಹಿನ್ನಲೆಯಲ್ಲಿ ಡೇಟಾವನ್ನು ಬಳಸಬಹುದು. ಇದರಿಂದ ಡೇಟಾ ಬಳಕೆ ಹೆಚ್ಚಾಗಬಹುದು."</string>
<string name="permlab_schedule_exact_alarm" msgid="6683283918033029730">"ನಿಖರವಾದ ಸಮಯೋಚಿತ ಕ್ರಿಯೆಗಳನ್ನು ನಿಗದಿಪಡಿಸಿ"</string>
<string name="permdesc_schedule_exact_alarm" msgid="8198009212013211497">"ಈ ಆ್ಯಪ್, ಭವಿಷ್ಯದಲ್ಲಿ ಕಾರ್ಯವು ಅಪೇಕ್ಷಿತ ಸಮಯಕ್ಕೆ ನಡೆಯುವಂತೆ ಕಾರ್ಯವನ್ನು ನಿಗದಿಪಡಿಸಬಹುದು. ಇದರರ್ಥ ನೀವು ಸಾಧನವನ್ನು ಸಕ್ರಿಯವಾಗಿ ಬಳಸದೇ ಇರುವಾಗ ಆ್ಯಪ್ ರನ್ ಆಗಬಹುದು."</string>
<string name="permlab_use_exact_alarm" msgid="348045139777131552">"ಅಲಾರಾಂಗಳು ಅಥವಾ ಈವೆಂಟ್ ರಿಮೈಂಡರ್‌ಗಳನ್ನು ನಿಗದಿಪಡಿಸಿ"</string>
<string name="permdesc_use_exact_alarm" msgid="7033761461886938912">"ಈ ಆ್ಯಪ್ ಭವಿಷ್ಯದಲ್ಲಿ ಅಪೇಕ್ಷಿತ ಸಮಯದಲ್ಲಿ ನಿಮಗೆ ತಿಳಿಸುವುದಕ್ಕಾಗಿ ಅಲಾರಾಂಗಳು ಮತ್ತು ರಿಮೈಂಡರ್‌ಗಳಂತಹ ಕ್ರಿಯೆಗಳನ್ನು ನಿಗದಿಪಡಿಸಬಹುದು."</string>
- <string name="permlab_persistentActivity" msgid="464970041740567970">"ಅಪ್ಲಿಕೇಶನ್‌‌ ಅನ್ನು ಯಾವಾಗಲೂ ರನ್‌ ಆಗುವಂತೆ ಮಾಡಿ"</string>
+ <string name="permlab_persistentActivity" msgid="464970041740567970">"ಆ್ಯಪ್‌ ಅನ್ನು ಯಾವಾಗಲೂ ರನ್‌ ಆಗುವಂತೆ ಮಾಡಿ"</string>
<string name="permdesc_persistentActivity" product="tablet" msgid="6055271149187369916">"ಸ್ಮರಣೆಯಲ್ಲಿ ನಿರಂತರವಾಗಿ ತನ್ನದೇ ಭಾಗಗಳನ್ನು ಮಾಡಲು ಅಪ್ಲಿಕೇಶನ್‍‍ಗೆ ಅವಕಾಶ ಮಾಡಿಕೊಡುತ್ತದೆ. ಇದು ಟ್ಯಾಬ್ಲೆಟ್ ಕಾರ್ಯವನ್ನು ನಿಧಾನಗೊಳಿಸುವುದರ ಮೂಲಕ ಇತರ ಅಪ್ಲಿಕೇಶನ್‍‍ಗಳಿಗೆ ಲಭ್ಯವಿರುವ ಸ್ಮರಣೆಯನ್ನು ಮಿತಿಗೊಳಿಸುತ್ತದೆ."</string>
<string name="permdesc_persistentActivity" product="tv" msgid="6800526387664131321">"ಮೆಮೊರಿಯಲ್ಲಿ ತನ್ನ ಭಾಗಗಳನ್ನು ನಿರಂತರವಾಗಿರಿಸಲು ಅಪ್ಲಿಕೇಶನ್‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ. ಇದು ಇತರ ಅಪ್ಲಿಕೇಶನ್‌ಗಳಿಗೆ ಲಭ್ಯವಿರುವ ಮೆಮೊರಿಯನ್ನು ಮಿತಿಗೊಳಿಸಿ Android TV ಸಾಧನವನ್ನು ಇದು ನಿಧಾನಗೊಳಿಸಬಹುದು."</string>
<string name="permdesc_persistentActivity" product="default" msgid="1914841924366562051">"ಸ್ಮರಣೆಯಲ್ಲಿ ನಿರಂತರವಾಗಿ ತನ್ನದೇ ಭಾಗಗಳನ್ನು ಮಾಡಲು ಅಪ್ಲಿಕೇಶನ್‍‍ಗೆ ಅವಕಾಶ ಮಾಡಿಕೊಡುತ್ತದೆ. ಇದು ಫೋನ್ ಕಾರ್ಯವನ್ನು ನಿಧಾನಗೊಳಿಸುವುದರ ಮೂಲಕ ಇತರ ಅಪ್ಲಿಕೇಶನ್‍‍ಗಳಿಗೆ ಲಭ್ಯವಿರುವ ಸ್ಮರಣೆಯನ್ನು ಮಿತಿಗೊಳಿಸುತ್ತದೆ."</string>
@@ -457,14 +457,14 @@
<string name="permdesc_foregroundServiceMediaProcessing" msgid="8303086172106677312">"\"mediaProcessing\" ಪ್ರಕಾರದೊಂದಿಗೆ ಮುನ್ನೆಲೆ ಸೇವೆಗಳನ್ನು ಬಳಸಲು ಆ್ಯಪ್‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ"</string>
<string name="permlab_foregroundServiceSpecialUse" msgid="7973536745876645082">"\"specialUse\" ಪ್ರಕಾರದೊಂದಿಗೆ ಮುನ್ನೆಲೆ ಸೇವೆಯನ್ನು ರನ್ ಮಾಡಿ"</string>
<string name="permdesc_foregroundServiceSpecialUse" msgid="646713654541885919">"\"specialUse\" ಪ್ರಕಾರದೊಂದಿಗೆ ಮುನ್ನೆಲೆ ಸೇವೆಗಳನ್ನು ಬಳಸಲು ಆ್ಯಪ್‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ"</string>
- <string name="permlab_getPackageSize" msgid="375391550792886641">"ಅಪ್ಲಿಕೇಶನ್‌ ಸಂಗ್ರಹ ಸ್ಥಳವನ್ನು ಅಳೆಯಿರಿ"</string>
+ <string name="permlab_getPackageSize" msgid="375391550792886641">"ಆ್ಯಪ್ ಸಂಗ್ರಹ ಸ್ಥಳವನ್ನು ಅಳೆಯಿರಿ"</string>
<string name="permdesc_getPackageSize" msgid="742743530909966782">"ಅದರ ಕೋಡ್‌‌, ಡೇಟಾ, ಮತ್ತು ಕ್ಯಾಷ್‌ ಗಾತ್ರಗಳನ್ನು ಹಿಂಪಡೆಯಲು ಅಪ್ಲಿಕೇಶನ್‌‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ"</string>
<string name="permlab_writeSettings" msgid="8057285063719277394">"ಸಿಸ್ಟಂ ಸೆಟ್ಟಿಂಗ್‍ಗಳನ್ನು ಮಾರ್ಪಡಿಸಿ"</string>
<string name="permdesc_writeSettings" msgid="8293047411196067188">"ಸಿಸ್ಟಂನ ಸೆಟ್ಟಿಂಗ್‌ಗಳ ಡೇಟಾವನ್ನು ಮಾರ್ಪಡಿಸಲು ಅಪ್ಲಿಕೇಶನ್‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ. ದುರುದ್ದೇಶಪೂರಿತ ಅಪ್ಲಿಕೇಶನ್‌ಗಳು ನಿಮ್ಮ ಸಿಸ್ಟಂನ ಕಾನ್ಫಿಗಿರೆಶನ್‌ ಅನ್ನು ಹಾನಿ ಮಾಡಬಹುದು."</string>
<string name="permlab_receiveBootCompleted" msgid="6643339400247325379">"ಪ್ರಾರಂಭದಲ್ಲಿ ರನ್ ಮಾಡಿ"</string>
- <string name="permdesc_receiveBootCompleted" product="tablet" msgid="5565659082718177484">"ಸಿಸ್ಟಂ ಬೂಟ್ ಮಾಡುವುದನ್ನು ಮುಗಿಸಿದ ನಂತರ ಅಪ್ಲಿಕೇಶನ್ ಅನ್ನು ತಾನಾಗಿಯೇ ಪ್ರಾರಂಭಿಸಲು ಅನುಮತಿಸುತ್ತದೆ. ಈ ಕಾರಣದಿಂದಾಗಿ ಟ್ಯಾಬ್ಲೆಟ್ ಪ್ರಾರಂಭಿಸಲು ಇದಕ್ಕೆ ಹೆಚ್ಚು ಸಮಯ ತೆಗೆದುಕೊಳ್ಳಬಹುದು ಮತ್ತು ಯಾವಾಗಲೂ ರನ್ ಆಗುವ ಮೂಲಕ ಒಟ್ಟು ಮೊತ್ತ ಟ್ಯಾಬ್ಲೆಟ್‌ನ ವೇಗವನ್ನು ಕಡಿಮೆ ಮಾಡಲು ಅನುಮತಿಸುತ್ತದೆ."</string>
+ <string name="permdesc_receiveBootCompleted" product="tablet" msgid="5565659082718177484">"ಸಿಸ್ಟಂ ಬೂಟ್ ಮಾಡುವುದನ್ನು ಮುಗಿಸಿದ ನಂತರ ಆ್ಯಪ್‌ ಅನ್ನು ತಾನಾಗಿಯೇ ಪ್ರಾರಂಭಿಸಲು ಅನುಮತಿಸುತ್ತದೆ. ಈ ಕಾರಣದಿಂದಾಗಿ ಟ್ಯಾಬ್ಲೆಟ್ ಪ್ರಾರಂಭಿಸಲು ಇದಕ್ಕೆ ಹೆಚ್ಚು ಸಮಯ ತೆಗೆದುಕೊಳ್ಳಬಹುದು ಮತ್ತು ಯಾವಾಗಲೂ ರನ್ ಆಗುವ ಮೂಲಕ ಒಟ್ಟು ಮೊತ್ತ ಟ್ಯಾಬ್ಲೆಟ್‌ನ ವೇಗವನ್ನು ಕಡಿಮೆ ಮಾಡಲು ಆ್ಯಪ್‌ ಅನುಮತಿಸುತ್ತದೆ."</string>
<string name="permdesc_receiveBootCompleted" product="tv" msgid="4900842256047614307">"ಸಿಸ್ಟಂ ಬೂಟ್ ಮಾಡುವುದನ್ನು ಪೂರ್ಣಗೊಳಿಸಿದ ತಕ್ಷಣ ಸ್ವತಃ ಪ್ರಾರಂಭವಾಗಲು ಅಪ್ಲಿಕೇಶನ್‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ. ಇದು Android TV ಸಾಧನವನ್ನು ಪ್ರಾರಂಭಿಸಲು ಹೆಚ್ಚು ಸಮಯ ತೆಗೆದುಕೊಳ್ಳುವಂತೆ ಮಾಡಬಹುದು ಮತ್ತು ಯಾವಾಗಲೂ ರನ್ ಆಗುವ ಮೂಲಕ ಒಟ್ಟಾರೆ ಸಾಧನವನ್ನು ನಿಧಾನಗೊಳಿಸಲು ಅಪ್ಲಿಕೇಶನ್‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ."</string>
- <string name="permdesc_receiveBootCompleted" product="default" msgid="7912677044558690092">"ಸಿಸ್ಟಂ ಬೂಟ್ ಮಾಡುವುದನ್ನು ಮುಗಿಸಿದ ನಂತರ ಅಪ್ಲಿಕೇಶನ್ ಅನ್ನು ತಾನಾಗಿಯೇ ಪ್ರಾರಂಭಿಸಲು ಅನುಮತಿಸುತ್ತದೆ. ಈ ಕಾರಣದಿಂದಾಗಿ ಫೋನ್‌ ಪ್ರಾರಂಭಿಸಲು ಇದಕ್ಕೆ ಹೆಚ್ಚು ಸಮಯ ತೆಗೆದುಕೊಳ್ಳಬಹುದು ಮತ್ತು ಯಾವಾಗಲೂ ರನ್ ಆಗುವ ಮೂಲಕ ಒಟ್ಟು ಮೊತ್ತ ಫೋನ್‌ನ ವೇಗವನ್ನು ಕಡಿಮೆ ಮಾಡಲು ಅನುಮತಿಸುತ್ತದೆ."</string>
+ <string name="permdesc_receiveBootCompleted" product="default" msgid="7912677044558690092">"ಸಿಸ್ಟಂ ಬೂಟ್ ಮಾಡುವುದನ್ನು ಮುಗಿಸಿದ ನಂತರ ಆ್ಯಪ್‌ ಅನ್ನು ತಾನಾಗಿಯೇ ಪ್ರಾರಂಭಿಸಲು ಅನುಮತಿಸುತ್ತದೆ. ಈ ಕಾರಣದಿಂದಾಗಿ ಫೋನ್‌ ಪ್ರಾರಂಭಿಸಲು ಇದಕ್ಕೆ ಹೆಚ್ಚು ಸಮಯ ತೆಗೆದುಕೊಳ್ಳಬಹುದು ಮತ್ತು ಯಾವಾಗಲೂ ರನ್ ಆಗುವ ಮೂಲಕ ಒಟ್ಟು ಮೊತ್ತ ಫೋನ್‌ನ ವೇಗವನ್ನು ಕಡಿಮೆ ಮಾಡಲು ಆ್ಯಪ್‌ ಅನುಮತಿಸುತ್ತದೆ."</string>
<string name="permlab_broadcastSticky" msgid="4552241916400572230">"ಸ್ಟಿಕಿ ಪ್ರಸಾರವನ್ನು ಕಳುಹಿಸಿ"</string>
<string name="permdesc_broadcastSticky" product="tablet" msgid="5058486069846384013">"ಪ್ರಸಾರ ಕೊನೆಗೊಂಡ ನಂತರ ಹಾಗೆಯೇ ಉಳಿಯುವ ಸ್ಟಿಕಿ ಪ್ರಸಾರಗಳನ್ನು ಕಳುಹಿಸಲು ಅಪ್ಲಿಕೇಶನ್‍‍ಗೆ ಅನುಮತಿಸುತ್ತದೆ. ಮಿತಿಮೀರಿದ ಬಳಕೆಯು ಟ್ಯಾಬ್ಲೆಟ್ ಅನ್ನು ನಿಧಾನಗೊಳಿಸಬಹುದು ಅಥವಾ ಅತಿಯಾದ ಮೆಮೊರಿ ಬಳಕೆಯು ಅಸ್ಥಿರತೆಯನ್ನು ಉಂಟುಮಾಡಬಹುದು."</string>
<string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"ಪ್ರಸಾರವು ಮುಕ್ತಾಯಗೊಂಡ ನಂತರ ಉಳಿದಿರುವ ಜಿಗುಟಾದ ಪ್ರಸಾರಗಳನ್ನು ಕಳುಹಿಸಲು ಅಪ್ಲಿಕೇಶನ್‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ. ಇದರ ಹೆಚ್ಚಿನ ಬಳಕೆಯು Android TV ಸಾಧನವನ್ನು ನಿಧಾನಗೊಳಿಸುತ್ತದೆ ಅಥವಾ ಅತಿಯಾಗಿ ಮೆಮೊರಿಯನ್ನು ಬಳಸುವಂತೆ ಮಾಡುವ ಮೂಲಕ ಅಸ್ಥಿರಗೊಳಿಸುತ್ತದೆ."</string>
@@ -478,7 +478,7 @@
<string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"ನಿಮ್ಮ Android TV ಸಾಧನದಲ್ಲಿ ಸಂಗ್ರಹಿಸಲಾಗಿರುವ ನಿಮ್ಮ ಸಂಪರ್ಕಗಳ ಕುರಿತಾದ ಡೇಟಾವನ್ನು ಮಾರ್ಪಡಿಸಲು ಆ್ಯಪ್‌ಗೆ ಅವಕಾಶ ನೀಡುತ್ತದೆ. ಈ ಅನುಮತಿಯು ಸಂಪರ್ಕ ಡೇಟಾವನ್ನು ಅಳಿಸಲು ಆ್ಯಪ್‌ಗಳಿಗೆ ಅನುಮತಿಸುತ್ತದೆ."</string>
<string name="permdesc_writeContacts" product="default" msgid="8304795696237065281">"ನಿಮ್ಮ ಫೋನ್‍ನಲ್ಲಿ ಸಂಗ್ರಹಿಸಲಾಗಿರುವ ನಿಮ್ಮ ಸಂಪರ್ಕಗಳ ಕುರಿತಾದ ಡೇಟಾವನ್ನು ಮಾರ್ಪಡಿಸಲು ಆ್ಯಪ್‌ಗೆ ಅವಕಾಶ ನೀಡುತ್ತದೆ. ಈ ಅನುಮತಿಯು ಸಂಪರ್ಕ ಡೇಟಾವನ್ನು ಅಳಿಸಲು ಆ್ಯಪ್‌ಗಳಿಗೆ ಅನುಮತಿಸುತ್ತದೆ."</string>
<string name="permlab_readCallLog" msgid="1739990210293505948">"ಕರೆಯ ಲಾಗ್‌ ಓದಿ"</string>
- <string name="permdesc_readCallLog" msgid="8964770895425873433">"ಈ ಅಪ್ಲಿಕೇಶನ್ ನಿಮ್ಮ ಕರೆಯ ಇತಿಹಾಸ ಓದಬಹುದು."</string>
+ <string name="permdesc_readCallLog" msgid="8964770895425873433">"ಈ ಆ್ಯಪ್‌ ನಿಮ್ಮ ಕರೆಯ ಇತಿಹಾಸ ಓದಬಹುದು."</string>
<string name="permlab_writeCallLog" msgid="670292975137658895">"ಕರೆ ಲಾಗ್ ಬರೆಯಿರಿ"</string>
<string name="permdesc_writeCallLog" product="tablet" msgid="2657525794731690397">"ಒಳಬರುವ ಮತ್ತು ಹೊರಹೋಗುವ ಕರೆಗಳ ಕುರಿತ ಡೇಟಾ ಸೇರಿದಂತೆ, ನಿಮ್ಮ ಟ್ಯಾಬ್ಲೆಟ್‌ನ ಕರೆಯ ಲಾಗ್ ಅನ್ನು ಮಾರ್ಪಡಿಸಲು ಅಪ್ಲಿಕೇಶನ್‍‍ಗೆ ಅವಕಾಶ ನೀಡುತ್ತದೆ. ದುರುದ್ದೇಶಪೂರಿತ ಅಪ್ಲಿಕೇಶನ್‍‍ಗಳು ನಿಮ್ಮ ಕರೆಯ ಲಾಗ್‍ ಅನ್ನು ಅಳಿಸಲು ಅಥವಾ ಮಾರ್ಪಡಿಸಲು ಇದನ್ನು ಬಳಸಿಕೊಳ್ಳಬಹುದು."</string>
<string name="permdesc_writeCallLog" product="tv" msgid="3934939195095317432">"ಒಳಬರುವ ಮತ್ತು ಹೊರಹೋಗುವ ಕರೆಗಳ ಕುರಿತ ಡೇಟಾ ಸೇರಿದಂತೆ ನಿಮ್ಮ Android TV ಸಾಧನದ ಕರೆಯ ಲಾಗ್ ಅನ್ನು ಮಾರ್ಪಡಿಸಲು ಅಪ್ಲಿಕೇಶನ್‍‍ಗೆ ಅನುಮತಿಸುತ್ತದೆ. ದುರುದ್ದೇಶಪೂರಿತ ಅಪ್ಲಿಕೇಶನ್‍‍ಗಳು ನಿಮ್ಮ ಕರೆಯ ಲಾಗ್‍ ಅನ್ನು ಅಳಿಸಲು ಅಥವಾ ಮಾರ್ಪಡಿಸಲು ಇದನ್ನು ಬಳಸಿಕೊಳ್ಳಬಹುದು."</string>
@@ -488,13 +488,13 @@
<string name="permlab_bodySensors_background" msgid="4912560779957760446">"ಹಿನ್ನಲೆಯಲ್ಲಿರುವಾಗ ಹೃದಯ ಬಡಿತದಂತಹ ದೇಹದ ಸೆನ್ಸರ್‌ ಡೇಟಾವನ್ನು ಪ್ರವೇಶಿಸಿ"</string>
<string name="permdesc_bodySensors_background" product="default" msgid="8870726027557749417">"ಆ್ಯಪ್ ಹಿನ್ನಲೆಯಲ್ಲಿರುವಾಗ ಹೃದಯ ಬಡಿತ, ತಾಪಮಾನ ಮತ್ತು ರಕ್ತದ ಆಮ್ಲಜನಕದ ಶೇಕಡಾವಾರು ಎಂಬಂತಹ ದೇಹದ ಸೆನ್ಸರ್‌ ಡೇಟಾವನ್ನು ಪ್ರವೇಶಿಸಲು ಆ್ಯಪ್‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ."</string>
<string name="permlab_readCalendar" msgid="6408654259475396200">"ಕ್ಯಾಲೆಂಡರ್ ಈವೆಂಟ್‌ಗಳು ಮತ್ತು ವಿವರಗಳನ್ನು ಓದಿ"</string>
- <string name="permdesc_readCalendar" product="tablet" msgid="515452384059803326">"ಈ ಅಪ್ಲಿಕೇಶನ್ ನಿಮ್ಮ ಟ್ಯಾಬ್ಲೆಟ್‌ನಲ್ಲಿ ಸಂಗ್ರಹವಾಗಿರುವ ಎಲ್ಲಾ ಕ್ಯಾಲೆಂಡರ್ ಈವೆಂಟ್‌ಗಳನ್ನು ಓದಬಹುದು ಮತ್ತು ನಿಮ್ಮ ಕ್ಯಾಲೆಂಡರ್ ಡೇಟಾವನ್ನು ಹಂಚಿಕೊಳ್ಳಬಹುದು ಅಥವಾ ಉಳಿಸಬಹುದು."</string>
- <string name="permdesc_readCalendar" product="tv" msgid="5811726712981647628">"ಈ ಅಪ್ಲಿಕೇಶನ್ ನಿಮ್ಮ Android TV ಸಾಧನದಲ್ಲಿ ಸಂಗ್ರಹವಾಗಿರುವ ಎಲ್ಲಾ ಕ್ಯಾಲೆಂಡರ್ ಈವೆಂಟ್‌ಗಳನ್ನು ಓದಬಹುದು ಮತ್ತು ನಿಮ್ಮ ಕ್ಯಾಲೆಂಡರ್ ಡೇಟಾವನ್ನು ಹಂಚಿಕೊಳ್ಳಬಹುದು ಅಥವಾ ಉಳಿಸಬಹುದು."</string>
- <string name="permdesc_readCalendar" product="default" msgid="9118823807655829957">"ಈ ಅಪ್ಲಿಕೇಶನ್ ನಿಮ್ಮ ಫೋನ್‌ನಲ್ಲಿ ಸಂಗ್ರಹವಾಗಿರುವ ಎಲ್ಲಾ ಕ್ಯಾಲೆಂಡರ್ ಈವೆಂಟ್‌ಗಳನ್ನು ಓದಬಹುದು ಮತ್ತು ನಿಮ್ಮ ಕ್ಯಾಲೆಂಡರ್ ಡೇಟಾವನ್ನು ಹಂಚಿಕೊಳ್ಳಬಹುದು ಅಥವಾ ಉಳಿಸಬಹುದು."</string>
+ <string name="permdesc_readCalendar" product="tablet" msgid="515452384059803326">"ಈ ಆ್ಯಪ್‌ ನಿಮ್ಮ ಟ್ಯಾಬ್ಲೆಟ್‌ನಲ್ಲಿ ಸಂಗ್ರಹವಾಗಿರುವ ಎಲ್ಲಾ ಕ್ಯಾಲೆಂಡರ್ ಈವೆಂಟ್‌ಗಳನ್ನು ಓದಬಹುದು ಮತ್ತು ನಿಮ್ಮ ಕ್ಯಾಲೆಂಡರ್ ಡೇಟಾವನ್ನು ಹಂಚಿಕೊಳ್ಳಬಹುದು ಅಥವಾ ಉಳಿಸಬಹುದು."</string>
+ <string name="permdesc_readCalendar" product="tv" msgid="5811726712981647628">"ಈ ಆ್ಯಪ್‌ ನಿಮ್ಮ Android TV ಸಾಧನದಲ್ಲಿ ಸಂಗ್ರಹವಾಗಿರುವ ಎಲ್ಲಾ ಕ್ಯಾಲೆಂಡರ್ ಈವೆಂಟ್‌ಗಳನ್ನು ಓದಬಹುದು ಮತ್ತು ನಿಮ್ಮ ಕ್ಯಾಲೆಂಡರ್ ಡೇಟಾವನ್ನು ಹಂಚಿಕೊಳ್ಳಬಹುದು ಅಥವಾ ಉಳಿಸಬಹುದು."</string>
+ <string name="permdesc_readCalendar" product="default" msgid="9118823807655829957">"ಈ ಆ್ಯಪ್‌ ನಿಮ್ಮ ಫೋನ್‌ನಲ್ಲಿ ಸಂಗ್ರಹವಾಗಿರುವ ಎಲ್ಲಾ ಕ್ಯಾಲೆಂಡರ್ ಈವೆಂಟ್‌ಗಳನ್ನು ಓದಬಹುದು ಮತ್ತು ನಿಮ್ಮ ಕ್ಯಾಲೆಂಡರ್ ಡೇಟಾವನ್ನು ಹಂಚಿಕೊಳ್ಳಬಹುದು ಅಥವಾ ಉಳಿಸಬಹುದು."</string>
<string name="permlab_writeCalendar" msgid="6422137308329578076">"ಮಾಲೀಕರ ಗಮನಕ್ಕೆ ತರದೆಯೇ ಕ್ಯಾಲೆಂಡರ್ ಈವೆಂಟ್‌ಗಳನ್ನು ಸೇರಿಸಿ ಅಥವಾ ಮಾರ್ಪಡಿಸಿ ಮತ್ತು ಅತಿಥಿಗಳಿಗೆ ಇಮೇಲ್ ಕಳುಹಿಸಿ"</string>
- <string name="permdesc_writeCalendar" product="tablet" msgid="8722230940717092850">"ಈ ಅಪ್ಲಿಕೇಶನ್ ನಿಮ್ಮ ಟ್ಯಾಬ್ಲೆಟ್‌ನಲ್ಲಿ ಕ್ಯಾಲೆಂಡರ್ ಈವೆಂಟ್‌ಗಳನ್ನು ಸೇರಿಸಬಹುದು, ತೆಗೆದುಹಾಕಬಹುದು ಅಥವಾ ಬದಲಾಯಿಸಬಹುದು. ಈ ಅಪ್ಲಿಕೇಶನ್ ಕ್ಯಾಲೆಂಡರ್‌ನ ಮಾಲೀಕರಿಂದ ಬಂದಿರಬಹುದಾದ ಕಾಣಿಸಿಕೊಳ್ಳುವ ಸಂದೇಶಗಳನ್ನು ಕಳುಹಿಸಬಹುದು ಅಥವಾ ಅವರ ಮಾಲೀಕರಿಗೆ ತಿಳಿಸದಂತೆ ಘಟನೆಗಳು ಬದಲಾಯಿಸುವುದು."</string>
- <string name="permdesc_writeCalendar" product="tv" msgid="951246749004952706">"ಈ ಅಪ್ಲಿಕೇಶನ್ ನಿಮ್ಮ Android TV ಸಾಧನದಲ್ಲಿ ಕ್ಯಾಲೆಂಡರ್ ಈವೆಂಟ್‌ಗಳನ್ನು ಸೇರಿಸಬಹುದು, ತೆಗೆದುಹಾಕಬಹುದು ಅಥವಾ ಬದಲಾಯಿಸಬಹುದು. ಈ ಅಪ್ಲಿಕೇಶನ್ ಕ್ಯಾಲೆಂಡರ್‌ನ ಮಾಲೀಕರಿಂದ ಬರುವಂತೆ ಕಾಣುವ ಸಂದೇಶಗಳನ್ನು ಕಳುಹಿಸಬಹುದು ಅಥವಾ ಅದರ ಮಾಲೀಕರಿಗೆ ನೋಟಿಫಿಕೇಶನ್ ನೀಡದೆ ಈವೆಂಟ್‌ಗಳನ್ನು ಬದಲಾಯಿಸಬಹುದು."</string>
- <string name="permdesc_writeCalendar" product="default" msgid="5416380074475634233">"ಈ ಅಪ್ಲಿಕೇಶನ್ ನಿಮ್ಮ ಫೋನ್‌ನಲ್ಲಿ ಕ್ಯಾಲೆಂಡರ್ ಈವೆಂಟ್‌ಗಳನ್ನು ಸೇರಿಸಬಹುದು, ತೆಗೆದುಹಾಕಬಹುದು ಅಥವಾ ಬದಲಾಯಿಸಬಹುದು. ಈ ಅಪ್ಲಿಕೇಶನ್ ಕ್ಯಾಲೆಂಡರ್‌ನ ಮಾಲೀಕರಿಂದ ಬಂದಿರಬಹುದಾದ ಕಾಣಿಸಿಕೊಳ್ಳುವ ಸಂದೇಶಗಳನ್ನು ಕಳುಹಿಸಬಹುದು ಅಥವಾ ಅವರ ಮಾಲೀಕರಿಗೆ ತಿಳಿಸದಂತೆ ಘಟನೆಗಳು ಬದಲಾಯಿಸುವುದು."</string>
+ <string name="permdesc_writeCalendar" product="tablet" msgid="8722230940717092850">"ಈ ಆ್ಯಪ್‌ ನಿಮ್ಮ ಟ್ಯಾಬ್ಲೆಟ್‌ನಲ್ಲಿ ಕ್ಯಾಲೆಂಡರ್ ಈವೆಂಟ್‌ಗಳನ್ನು ಸೇರಿಸಬಹುದು, ತೆಗೆದುಹಾಕಬಹುದು ಅಥವಾ ಬದಲಾಯಿಸಬಹುದು. ಈ ಆ್ಯಪ್‌ ಕ್ಯಾಲೆಂಡರ್‌ನ ಮಾಲೀಕರಿಂದ ಬಂದಿರಬಹುದಾದ ಕಾಣಿಸಿಕೊಳ್ಳುವ ಸಂದೇಶಗಳನ್ನು ಕಳುಹಿಸಬಹುದು ಅಥವಾ ಅವರ ಮಾಲೀಕರಿಗೆ ತಿಳಿಸದಂತೆ ಘಟನೆಗಳು ಬದಲಾಯಿಸುವುದು."</string>
+ <string name="permdesc_writeCalendar" product="tv" msgid="951246749004952706">"ಈ ಆ್ಯಪ್‌ ನಿಮ್ಮ Android TV ಸಾಧನದಲ್ಲಿ ಕ್ಯಾಲೆಂಡರ್ ಈವೆಂಟ್‌ಗಳನ್ನು ಸೇರಿಸಬಹುದು, ತೆಗೆದುಹಾಕಬಹುದು ಅಥವಾ ಬದಲಾಯಿಸಬಹುದು. ಈ ಆ್ಯಪ್‌ ಕ್ಯಾಲೆಂಡರ್‌ನ ಮಾಲೀಕರಿಂದ ಬರುವಂತೆ ಕಾಣುವ ಸಂದೇಶಗಳನ್ನು ಕಳುಹಿಸಬಹುದು ಅಥವಾ ಅದರ ಮಾಲೀಕರಿಗೆ ನೋಟಿಫಿಕೇಶನ್ ನೀಡದೆ ಈವೆಂಟ್‌ಗಳನ್ನು ಬದಲಾಯಿಸಬಹುದು."</string>
+ <string name="permdesc_writeCalendar" product="default" msgid="5416380074475634233">"ಈ ಆ್ಯಪ್‌ ನಿಮ್ಮ ಫೋನ್‌ನಲ್ಲಿ ಕ್ಯಾಲೆಂಡರ್ ಈವೆಂಟ್‌ಗಳನ್ನು ಸೇರಿಸಬಹುದು, ತೆಗೆದುಹಾಕಬಹುದು ಅಥವಾ ಬದಲಾಯಿಸಬಹುದು. ಈ ಆ್ಯಪ್‌ ಕ್ಯಾಲೆಂಡರ್‌ನ ಮಾಲೀಕರಿಂದ ಬಂದಿರಬಹುದಾದ ಕಾಣಿಸಿಕೊಳ್ಳುವ ಸಂದೇಶಗಳನ್ನು ಕಳುಹಿಸಬಹುದು ಅಥವಾ ಅವರ ಮಾಲೀಕರಿಗೆ ತಿಳಿಸದಂತೆ ಘಟನೆಗಳು ಬದಲಾಯಿಸುವುದು."</string>
<string name="permlab_accessLocationExtraCommands" msgid="5162339812057983988">"ಹೆಚ್ಚುವರಿ ಸ್ಥಳ ಪೂರೈಕೆದಾರರ ಆದೇಶಗಳನ್ನು ಪ್ರವೇಶಿಸಿ"</string>
<string name="permdesc_accessLocationExtraCommands" msgid="355369611979907967">"ಹೆಚ್ಚಿನ ಸ್ಥಳ ಪೂರೈಕೆದಾರ ಆದೇಶಗಳನ್ನು ಪ್ರವೇಶಿಸಲು ಅಪ್ಲಿಕೇಶನ್‍‍ಗೆ ಅವಕಾಶ ನೀಡುತ್ತದೆ. ಇದು GPS ಅಥವಾ ಇತರ ಸ್ಥಳ ಮೂಲಗಳ ಕಾರ್ಯಾಚರಣೆಯಲ್ಲಿ ಮಧ್ಯ ಪ್ರವೇಶಿಸಲು ಅಪ್ಲಿಕೇಶನ್‍‍ಗೆ ಅನುಮತಿಸಬಹುದು."</string>
<string name="permlab_accessFineLocation" msgid="6426318438195622966">"ಮುನ್ನೆಲೆಯಲ್ಲಿ ಮಾತ್ರ ನಿಖರವಾದ ಸ್ಥಳವನ್ನು ಪ್ರವೇಶಿಸಿ"</string>
@@ -531,7 +531,7 @@
<string name="permlab_callPhone" msgid="1798582257194643320">"ಫೋನ್ ಸಂಖ್ಯೆಗಳಿಗೆ ನೇರವಾಗಿ ಕರೆ ಮಾಡಿ"</string>
<string name="permdesc_callPhone" msgid="7892422187827695656">"ನಿಮ್ಮ ಹಸ್ತಕ್ಷೇಪವಿಲ್ಲದೆಯೇ ಫೋನ್ ಸಂಖ್ಯೆಗಳಿಗೆ ಕರೆ ಮಾಡಲು ಆ್ಯಪ್‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ. ಇದು ಅನಿರೀಕ್ಷಿತ ಶುಲ್ಕಗಳು ಅಥವಾ ಕರೆಗಳಿಗೆ ಕಾರಣವಾಗಬಹುದು. ತುರ್ತು ಸಂಖ್ಯೆಗಳಿಗೆ ಕರೆ ಮಾಡಲು ಇದು ಆ್ಯಪ್‌ಗೆ ಅನುಮತಿಸುವುದಿಲ್ಲ ಎಂಬುದನ್ನು ಗಮನಿಸಿ. ದುರುದ್ದೇಶಪ್ರೇರಿತ ಆ್ಯಪ್‌ಗಳು ನಿಮ್ಮ ದೃಢೀಕರಣವಿಲ್ಲದೆಯೇ ಕರೆಗಳನ್ನು ಮಾಡುವ ಮೂಲಕ ಅಥವಾ ಒಳಬರುವ ಕರೆಗಳನ್ನು ಮತ್ತೊಂದು ಸಂಖ್ಯೆಗೆ ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಫಾರ್ವರ್ಡ್ ಮಾಡಲು ಕಾರಣವಾಗುವ ವಾಹಕದ ಕೋಡ್‌ಗಳನ್ನು ಡಯಲ್ ಮಾಡುವ ಮೂಲಕ ನಿಮ್ಮ ಹಣ ವೆಚ್ಚವಾಗುವಂತೆ ಮಾಡಬಹುದು."</string>
<string name="permlab_accessImsCallService" msgid="442192920714863782">"IMS ಕರೆ ಸೇವೆಯನ್ನು ಪ್ರವೇಶಿಸಿ"</string>
- <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"ನಿಮ್ಮ ಮಧ್ಯಸ್ಥಿಕೆ ಇಲ್ಲದೆಯೇ ಕರೆಗಳನ್ನು ಮಾಡಲು IMS ಸೇವೆಯನ್ನು ಬಳಸಲು ಅಪ್ಲಿಕೇಶನ್ ಅನ್ನು ಅನುಮತಿಸುತ್ತದೆ."</string>
+ <string name="permdesc_accessImsCallService" msgid="6328551241649687162">"ನಿಮ್ಮ ಮಧ್ಯಸ್ಥಿಕೆ ಇಲ್ಲದೆಯೇ ಕರೆಗಳನ್ನು ಮಾಡಲು IMS ಸೇವೆಯನ್ನು ಬಳಸಲು ಆ್ಯಪ್‌ ಅನ್ನು ಅನುಮತಿಸುತ್ತದೆ."</string>
<string name="permlab_readPhoneState" msgid="8138526903259297969">"ಫೋನ್ ಸ್ಥಿತಿ ಮತ್ತು ಗುರುತಿಸುವಿಕೆಯನ್ನು ಓದಿ"</string>
<string name="permdesc_readPhoneState" msgid="7229063553502788058">"ಸಾಧನದ ಫೋನ್ ವೈಶಿಷ್ಟ್ಯಗಳನ್ನು ಪ್ರವೇಶಿಸಲು ಅಪ್ಲಿಕೇಶನ್‍‍ಗೆ ಅವಕಾಶ ಮಾಡಿಕೊಡುತ್ತದೆ. ಈ ಅನುಮತಿಯು ಫೋನ್ ಸಂಖ್ಯೆ ಮತ್ತು ಸಾಧನದ ID ಗಳನ್ನು ನಿರ್ಧರಿಸಲು, ಕರೆಯು ಸಕ್ರಿಯವಾಗಿದೆಯೇ ಮತ್ತು ಕರೆಯ ಮೂಲಕ ರಿಮೋಟ್ ಸಂಖ್ಯೆಯು ಸಂಪರ್ಕಗೊಂಡಿವೆಯೇ ಎಂಬುದನ್ನೂ ನಿರ್ಧರಿಸಲು ಅಪ್ಲಿಕೇಶನ್‌ಗೆ ಅವಕಾಶ ಕಲ್ಪಿಸುತ್ತದೆ."</string>
<string name="permlab_readBasicPhoneState" msgid="3214853233263871347">"ಮೂಲ ಟೆಲಿಫೋನ್ ಸ್ಥಿತಿ ಮತ್ತು ಗುರುತನ್ನು ಓದಿ"</string>
@@ -542,7 +542,7 @@
<string name="permdesc_callCompanionApp" msgid="8474168926184156261">"ಸಾಧನದಲ್ಲಿನ ಚಾಲ್ತಿಯಲ್ಲಿರುವ ಕರೆಗಳನ್ನು ವೀಕ್ಷಿಸಲು ಮತ್ತು ನಿಯಂತ್ರಿಸಲು ಆ್ಯಪ್ ಅನುಮತಿಸುತ್ತದೆ. ಕರೆಗಳಿಗೆ ಸಂಬಂಧಿಸಿದ ಕರೆ ಸಂಖ್ಯೆಗಳು ಮತ್ತು ಕರೆ ಮಾಡಿದ ರಾಜ್ಯದಂತಹ ಮಾಹಿತಿಯನ್ನು ಇದು ಒಳಗೊಂಡಿರುತ್ತದೆ."</string>
<string name="permlab_exemptFromAudioRecordRestrictions" msgid="1164725468350759486">"ಆಡಿಯೋ ರೆಕಾರ್ಡ್ ಮಾಡುವ ನಿರ್ಬಂಧಗಳಿಂದ ವಿನಾಯಿತಿ ನೀಡಿ"</string>
<string name="permdesc_exemptFromAudioRecordRestrictions" msgid="2425117015896871976">"ಆಡಿಯೋ ರೆಕಾರ್ಡ್ ಮಾಡುವ ನಿರ್ಬಂಧದಿಂದ ಈ ಆ್ಯಪ್‌ಗೆ ವಿನಾಯಿತಿ ನೀಡಿ."</string>
- <string name="permlab_acceptHandover" msgid="2925523073573116523">"ಮತ್ತೊಂದು ಅಪ್ಲಿಕೇಶನ್‌ ಮೂಲಕ ಕರೆಯನ್ನು ಮುಂದುವರಿಸಿ"</string>
+ <string name="permlab_acceptHandover" msgid="2925523073573116523">"ಮತ್ತೊಂದು ಆ್ಯಪ್ ಮೂಲಕ ಕರೆಯನ್ನು ಮುಂದುವರಿಸಿ"</string>
<string name="permdesc_acceptHandovers" msgid="7129026180128626870">"ಮತ್ತೊಂದು ಅಪ್ಲಿಕೇಶನ್‌ನಲ್ಲಿ ಪ್ರಾರಂಭವಾದ ಕರೆಯನ್ನು ಮುಂದುವರಿಸಲು ಅಪ್ಲಿಕೇಶನ್‌ಗೆ ಅವಕಾಶ ಮಾಡಿಕೊಡಿ."</string>
<string name="permlab_readPhoneNumbers" msgid="5668704794723365628">"ಫೋನ್‌ ಸಂಖ್ಯೆಗಳನ್ನು ಓದಿ"</string>
<string name="permdesc_readPhoneNumbers" msgid="7368652482818338871">"ಸಾಧನದ ಫೋನ್ ಸಂಖ್ಯೆಗಳಿಗೆ ಆ್ಯಕ್ಸೆಸ್ ಪಡೆಯಲು ಅಪ್ಲಿಕೇಶನ್‌ಗೆ ಅನುಮತಿ ನೀಡುತ್ತದೆ."</string>
@@ -569,15 +569,15 @@
<string name="permdesc_setTimeZone" product="tv" msgid="9069045914174455938">"ನಿಮ್ಮ Android TV ಸಾಧನದ ಸಮಯವಲಯವನ್ನು ಬದಲಾಯಿಸಲು ಅಪ್ಲಿಕೇಶನ್‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ."</string>
<string name="permdesc_setTimeZone" product="default" msgid="4611828585759488256">"ಫೋನ್‌ನ ಸಮಯ ವಲಯವನ್ನು ಬದಲಾಯಿಸಲು ಅಪ್ಲಿಕೇಶನ್‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ."</string>
<string name="permlab_getAccounts" msgid="5304317160463582791">"ಸಾಧನದಲ್ಲಿ ಖಾತೆಗಳನ್ನು ಹುಡುಕಿ"</string>
- <string name="permdesc_getAccounts" product="tablet" msgid="1784452755887604512">"ಟ್ಯಾಬ್ಲೆಟ್ ಮೂಲಕ ತಿಳಿದಿರುವ ಖಾತೆಗಳ ಪಟ್ಟಿಯನ್ನು ಪಡೆದುಕೊಳ್ಳಲು ಅಪ್ಲಿಕೇಶನ್‍‍ಗೆ ಅವಕಾಶ ಮಾಡಿಕೊಡುತ್ತದೆ. ಇದು ನೀವು ಸ್ಥಾಪಿಸಿರುವ ಅಪ್ಲಿಕೇಶನ್ ಮೂಲಕ ರಚಿಸಲಾದ ಯಾವುದೇ ಖಾತೆಯನ್ನು ಒಳಗೊಂಡಿರಬಹುದು."</string>
+ <string name="permdesc_getAccounts" product="tablet" msgid="1784452755887604512">"ಟ್ಯಾಬ್ಲೆಟ್ ಮೂಲಕ ತಿಳಿದಿರುವ ಖಾತೆಗಳ ಪಟ್ಟಿಯನ್ನು ಪಡೆದುಕೊಳ್ಳಲು ಆ್ಯಪ್‌ಗೆ ಅವಕಾಶ ಮಾಡಿಕೊಡುತ್ತದೆ. ಇದು ನೀವು ಸ್ಥಾಪಿಸಿರುವ ಆ್ಯಪ್‌ ಮೂಲಕ ರಚಿಸಲಾದ ಯಾವುದೇ ಖಾತೆಯನ್ನು ಒಳಗೊಂಡಿರಬಹುದು."</string>
<string name="permdesc_getAccounts" product="tv" msgid="437604680436540822">"ನಿಮ್ಮ Android TV ಸಾಧನವು ತಿಳಿದಿರುವ ಖಾತೆಗಳ ಪಟ್ಟಿಯನ್ನು ಪಡೆಯಲು ಅಪ್ಲಿಕೇಶನ್‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ. ನೀವು ಇನ್‌ಸ್ಟಾಲ್ ಮಾಡಿದ ಅಪ್ಲಿಕೇಶನ್‌ಗಳಿಂದ ರಚಿಸಲಾದ ಯಾವುದೇ ಖಾತೆಗಳನ್ನು ಇದು ಒಳಗೊಂಡಿರಬಹುದು."</string>
- <string name="permdesc_getAccounts" product="default" msgid="2491273043569751867">"ಫೋನ್‌ನ ಮೂಲಕ ತಿಳಿದಿರುವ ಖಾತೆಗಳ ಪಟ್ಟಿಯನ್ನು ಪಡೆದುಕೊಳ್ಳಲು ಅಪ್ಲಿಕೇಶನ್‍‍ಗೆ ಅವಕಾಶ ಮಾಡಿಕೊಡುತ್ತದೆ. ಇದು ನೀವು ಸ್ಥಾಪಿಸಿರುವ ಅಪ್ಲಿಕೇಶನ್ ಮೂಲಕ ರಚಿಸಲಾದ ಯಾವುದೇ ಖಾತೆಯನ್ನು ಒಳಗೊಂಡಿರಬಹುದು."</string>
+ <string name="permdesc_getAccounts" product="default" msgid="2491273043569751867">"ಫೋನ್‌ನ ಮೂಲಕ ತಿಳಿದಿರುವ ಖಾತೆಗಳ ಪಟ್ಟಿಯನ್ನು ಪಡೆದುಕೊಳ್ಳಲು ಆ್ಯಪ್‌ಗೆ ಅವಕಾಶ ಮಾಡಿಕೊಡುತ್ತದೆ. ಇದು ನೀವು ಸ್ಥಾಪಿಸಿರುವ ಅಪ್ಲಿಕೇಶನ್ ಮೂಲಕ ರಚಿಸಲಾದ ಯಾವುದೇ ಖಾತೆಯನ್ನು ಒಳಗೊಂಡಿರಬಹುದು."</string>
<string name="permlab_accessNetworkState" msgid="2349126720783633918">"ನೆಟ್‍ವರ್ಕ್ ಸಂಪರ್ಕಗಳನ್ನು ವೀಕ್ಷಿಸಿ"</string>
<string name="permdesc_accessNetworkState" msgid="4394564702881662849">"ಅಸ್ತಿತ್ವದಲ್ಲಿರುವ ಮತ್ತು ಸಂಪರ್ಕಗೊಂಡಿರುವ ಸಂಪರ್ಕಗಳಂತಹ ನೆಟ್‍‍ವರ್ಕ್ ಸಂಪರ್ಕಗಳ ಕುರಿತ ಮಾಹಿತಿಯನ್ನು ವೀಕ್ಷಿಸಲು ಅಪ್ಲಿಕೇಶನ್‍‍ಗೆ ಅನುಮತಿಸುತ್ತದೆ."</string>
<string name="permlab_createNetworkSockets" msgid="3224420491603590541">"ಪೂರ್ಣ ನೆಟ್‌ವರ್ಕ್ ಪ್ರವೇಶವನ್ನು ಹೊಂದಿರಿ"</string>
<string name="permdesc_createNetworkSockets" msgid="7722020828749535988">"ನೆಟ್‍‍ವರ್ಕ್ ಸಾಕೆಟ್‍‍ಗಳನ್ನು ರಚಿಸಲು ಮತ್ತು ಕಸ್ಟಮ್ ನೆಟ್‍‍ವರ್ಕ್ ಪ್ರೊಟೋಕಾಲ್‍‍ಗಳನ್ನು ಬಳಸಲು ಅಪ್ಲಿಕೇಶನ್‌ಗೆ ಅವಕಾಶ ಮಾಡಿಕೊಡುತ್ತದೆ. ಬ್ರೌಸರ್ ಮತ್ತು ಇತರ ಅಪ್ಲಿಕೇಶನ್‌ಗಳು ಇಂಟರ್ನೆಟ್‌ಗೆ ಡೇಟಾ ಕಳುಹಿಸಲು ಮಾರ್ಗವನ್ನುಂಟು ಮಾಡುತ್ತದೆ ಹಾಗಾಗಿ ಇಂಟರ್ನೆಟ್‌ಗೆ ಡೇಟಾ ಕಳುಹಿಸಲು ಈ ಅನುಮತಿ ಅಗತ್ಯವಿರುವುದಿಲ್ಲ."</string>
<string name="permlab_changeNetworkState" msgid="8945711637530425586">"ನೆಟ್‌ವರ್ಕ್ ಸಂಪರ್ಕತೆಯನ್ನು ಬದಲಾಯಿಸಿ"</string>
- <string name="permdesc_changeNetworkState" msgid="649341947816898736">"ನೆಟ್‌ವರ್ಕ್‌ ಸಂಪರ್ಕದ ಸ್ಥಿತಿಯನ್ನು ಬದಲಾಯಿಸಲು ಅಪ್ಲಿಕೇಶನ್‌ ಅನುಮತಿಸುತ್ತದೆ."</string>
+ <string name="permdesc_changeNetworkState" msgid="649341947816898736">"ನೆಟ್‌ವರ್ಕ್‌ ಸಂಪರ್ಕದ ಸ್ಥಿತಿಯನ್ನು ಬದಲಾಯಿಸಲು ಆ್ಯಪ್‌ ಅನುಮತಿಸುತ್ತದೆ."</string>
<string name="permlab_changeTetherState" msgid="9079611809931863861">"ಟೆಥರಡ್ ಸಂಪರ್ಕತೆಯನ್ನು ಬದಲಾಯಿಸಿ"</string>
<string name="permdesc_changeTetherState" msgid="3025129606422533085">"ಟೆಥರ್‌ ಮಾಡಲಾದ ನೆಟ್‌ವರ್ಕ್‌ ಸಂಪರ್ಕದ ಸ್ಥಿತಿಯನ್ನು ಬದಲಾಯಿಸಲು ಅಪ್ಲಿಕೇಶನ್‌‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ."</string>
<string name="permlab_accessWifiState" msgid="5552488500317911052">"ವೈ-ಫೈ ಸಂಪರ್ಕಗಳನ್ನು ವೀಕ್ಷಿಸಿ"</string>
@@ -589,15 +589,15 @@
<string name="permdesc_changeWifiMulticastState" product="tv" msgid="1336952358450652595">"ವೈ-ಫೈ ನೆಟ್‌ವರ್ಕ್‌ನಲ್ಲಿ ನಿಮ್ಮ Android TV ಮಾತ್ರವಲ್ಲದೆ, ಮಲ್ಟಿಕ್ಯಾಸ್ಟ್ ವಿಳಾಸಗಳನ್ನು ಬಳಸಿಕೊಂಡು ಎಲ್ಲಾ ಸಾಧನಗಳಿಗೆ ಕಳುಹಿಸಲಾದ ಪ್ಯಾಕೆಟ್‌ಗಳನ್ನು ಸ್ವೀಕರಿಸಲು ಅಪ್ಲಿಕೇಶನ್‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ. ಇದು ಮಲ್ಟಿಕ್ಯಾಸ್ಟ್ ಅಲ್ಲದ ಮೋಡ್‌ಗಿಂತಲೂ ಹೆಚ್ಚು ಪವರ್ ಬಳಸುತ್ತದೆ."</string>
<string name="permdesc_changeWifiMulticastState" product="default" msgid="8296627590220222740">"ನಿಮ್ಮ ಫೋನ್ ಮಾತ್ರವಲ್ಲದೇ, ಮಲ್ಟಿಕಾಸ್ಟ್ ವಿಳಾಸಗಳನ್ನು ಬಳಸಿಕೊಂಡು ವೈ-ಫೈ ನೆಟ್‍‍ವರ್ಕ್‌ನಲ್ಲಿ ಎಲ್ಲಾ ಸಾಧನಗಳಿಗೆ ಕಳುಹಿಸಲಾಗಿರುವ ಪ್ಯಾಕೆಟ್‍‍ಗಳನ್ನು ಸ್ವೀಕರಿಸಲು ಅಪ್ಲಿಕೇಶನ್‍‍ಗೆ ಅವಕಾಶ ನೀಡುತ್ತದೆ. ಇದು ಮಲ್ಟಿಕ್ಯಾಸ್ಟ್ ಅಲ್ಲದ ಮೋಡ್ ಬಳಸುವ ಶಕ್ತಿಗಿಂತಲೂ ಹೆಚ್ಚಿನ ಶಕ್ತಿಯನ್ನು ಬಳಸುತ್ತದೆ."</string>
<string name="permlab_bluetoothAdmin" msgid="6490373569441946064">"ಬ್ಲೂಟೂತ್‌ ಸೆಟ್ಟಿಂಗ್‍ಗಳನ್ನು ಪ್ರವೇಶಿಸಿ"</string>
- <string name="permdesc_bluetoothAdmin" product="tablet" msgid="5370837055438574863">"ಸ್ಥಳೀಯ ಬ್ಲೂಟೂತ್‌‌ ಟ್ಯಾಬ್ಲೆಟ್‌‌ ಕಾನ್ಫಿಗರ್‌ ಮಾಡಲು ಮತ್ತು ಅನ್ವೇಷಿಸಲು ಹಾಗೂ ರಿಮೊಟ್‌ ಸಾಧನಗಳ ಜೊತೆಗೆ ಜೋಡಿ ಮಾಡಲು ಅಪ್ಲಿಕೇಶನ್‌ ಅನುಮತಿಸುತ್ತದೆ."</string>
+ <string name="permdesc_bluetoothAdmin" product="tablet" msgid="5370837055438574863">"ಸ್ಥಳೀಯ ಬ್ಲೂಟೂತ್‌‌ ಟ್ಯಾಬ್ಲೆಟ್‌‌ ಕಾನ್ಫಿಗರ್‌ ಮಾಡಲು ಮತ್ತು ಅನ್ವೇಷಿಸಲು ಹಾಗೂ ರಿಮೊಟ್‌ ಸಾಧನಗಳ ಜೊತೆಗೆ ಜೋಡಿ ಮಾಡಲು ಆ್ಯಪ್‌ ಅನುಮತಿಸುತ್ತದೆ."</string>
<string name="permdesc_bluetoothAdmin" product="tv" msgid="1623992984547014588">"ನಿಮ್ಮ Android TV ಸಾಧನದಲ್ಲಿ ಬ್ಲೂಟೂತ್ ಅನ್ನು ಕಾನ್ಫಿಗರ್ ಮಾಡಲು ಮತ್ತು ರಿಮೋಟ್ ಸಾಧನಗಳನ್ನು ಕಂಡುಹಿಡಿಯಲು ಮತ್ತು ಅವುಗಳೊಂದಿಗೆ ಜೋಡಿಸಲು ಅಪ್ಲಿಕೇಶನ್‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ."</string>
<string name="permdesc_bluetoothAdmin" product="default" msgid="7381341743021234863">"ಸ್ಥಳೀಯ ಬ್ಲೂಟೂತ್‌‌ ಫೋನ್‌ ಕಾನ್ಫಿಗರ್‌ ಮಾಡಲು ಮತ್ತು ಅನ್ವೇಷಿಸಲು ಹಾಗೂ ರಿಮೊಟ್‌ ಸಾಧನಗಳ ಜೊತೆಗೆ ಜೋಡಿ ಮಾಡಲು ಅಪ್ಲಿಕೇಶನ್‌ಗೆ ಅವಕಾಶ ಮಾಡಿಕೊಡುತ್ತದೆ."</string>
<string name="permlab_accessWimaxState" msgid="7029563339012437434">"WiMAX ನಿಂದ ಸಂಪರ್ಕಗೊಳಿಸಿ ಮತ್ತು ಸಂಪರ್ಕ ಕಡಿತಗೊಳಿಸಿ"</string>
<string name="permdesc_accessWimaxState" msgid="5372734776802067708">"WiMAX ಸಕ್ರಿಯಗೊಂಡಿದೆಯೇ ಮತ್ತು ಸಂಪರ್ಕಗೊಂಡಿರುವಂತಹ WiMAX ನೆಟ್‍‍ವರ್ಕ್‌ಗಳ ಕುರಿತು ಮಾಹಿತಿಯನ್ನು ನಿರ್ಧರಿಸಲು ಅಪ್ಲಿಕೇಶನ್‍‍ಗೆ ಅವಕಾಶ ನೀಡುತ್ತದೆ."</string>
<string name="permlab_changeWimaxState" msgid="6223305780806267462">"WiMAX ಸ್ಥಿತಿಯನ್ನು ಬದಲಿಸಿ"</string>
- <string name="permdesc_changeWimaxState" product="tablet" msgid="4011097664859480108">"ಅಪ್ಲಿಕೇಶನ್ ಅನ್ನು ಟ್ಯಾಬ್ಲೆಟ್‌ಗೆ ಸಂಪರ್ಕಪಡಿಸಲು ಮತ್ತು WiMAX ನೆಟ್‍‍ವರ್ಕ್‌ಗಳಿಂದ ಟ್ಯಾಬ್ಲೆಟ್ ಅನ್ನು ಸಂಪರ್ಕ ಕಡಿತಗೊಳಿಸಲು ಅವಕಾಶ ಮಾಡಿಕೊಡುತ್ತದೆ."</string>
+ <string name="permdesc_changeWimaxState" product="tablet" msgid="4011097664859480108">"ಆ್ಯಪ್ ಅನ್ನು ಟ್ಯಾಬ್ಲೆಟ್‌ಗೆ ಸಂಪರ್ಕಪಡಿಸಲು ಮತ್ತು WiMAX ನೆಟ್‍‍ವರ್ಕ್‌ಗಳಿಂದ ಟ್ಯಾಬ್ಲೆಟ್ ಅನ್ನು ಸಂಪರ್ಕ ಕಡಿತಗೊಳಿಸಲು ಅವಕಾಶ ಮಾಡಿಕೊಡುತ್ತದೆ."</string>
<string name="permdesc_changeWimaxState" product="tv" msgid="5373274458799425276">"WiMAX ನೆಟ್‌ವರ್ಕ್‌ಗಳಿಗೆ ನಿಮ್ಮ Android TV ಸಾಧನವನ್ನು ಸಂಪರ್ಕಿಸಲು ಮತ್ತು ನಿಮ್ಮ Android TV ಸಾಧನವನ್ನು WiMAX ನೆಟ್‌ವರ್ಕ್‌ಗಳಿಂದ ಸಂಪರ್ಕ ಕಡಿತಗೊಳಿಸಲು ಅಪ್ಲಿಕೇಶನ್‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ."</string>
- <string name="permdesc_changeWimaxState" product="default" msgid="1551666203780202101">"ಅಪ್ಲಿಕೇಶನ್ ಅನ್ನು ಫೋನ್‌ಗೆ ಸಂಪರ್ಕಪಡಿಸಲು ಮತ್ತು WiMAX ನೆಟ್‍‍ವರ್ಕ್‌ಗಳಿಂದ ಫೋನ್ ಸಂಪರ್ಕ ಕಡಿತಗೊಳಿಸಲು ಅವಕಾಶ ಮಾಡಿಕೊಡುತ್ತದೆ."</string>
+ <string name="permdesc_changeWimaxState" product="default" msgid="1551666203780202101">"ಆ್ಯಪ್‌ ಅನ್ನು ಫೋನ್‌ಗೆ ಸಂಪರ್ಕಪಡಿಸಲು ಮತ್ತು WiMAX ನೆಟ್‍‍ವರ್ಕ್‌ಗಳಿಂದ ಫೋನ್ ಸಂಪರ್ಕ ಕಡಿತಗೊಳಿಸಲು ಅವಕಾಶ ಮಾಡಿಕೊಡುತ್ತದೆ."</string>
<string name="permlab_bluetooth" msgid="586333280736937209">"ಬ್ಲೂಟೂತ್‌ ಸಾಧನಗಳೊಂದಿಗೆ ಜೋಡಿಸಿ"</string>
<string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"ಟ್ಯಾಬ್ಲೆಟ್‍‍ನಲ್ಲಿ ಬ್ಲೂಟೂತ್‌‌ನ ಕಾನ್ಫಿಗರೇಶನ್ ಅನ್ನು ವೀಕ್ಷಿಸಲು ಮತ್ತು ಜೋಡಿ ಮಾಡಿರುವ ಸಾಧನಗಳೊಂದಿಗೆ ಸಂಪರ್ಕಗಳನ್ನು ಕಲ್ಪಿಸಲು ಹಾಗೂ ಸ್ವೀಕರಿಸಲು ಅಪ್ಲಿಕೇಶನ್‍‍ಗೆ ಅವಕಾಶ ನೀಡುತ್ತದೆ."</string>
<string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"ನಿಮ್ಮ Android TV ಸಾಧನದಲ್ಲಿ ಬ್ಲೂಟೂತ್‌ನ ಕಾನ್ಫಿಗರೇಶನ್ ವೀಕ್ಷಿಸಲು ಮತ್ತು ಜೋಡಿಸಿರುವ ಸಾಧನಗಳೊಂದಿಗೆ ಸಂಪರ್ಕಗಳನ್ನು ಮಾಡಲು ಮತ್ತು ಸ್ವೀಕರಿಸಲು ಅಪ್ಲಿಕೇಶನ್‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ."</string>
@@ -613,9 +613,9 @@
<string name="permlab_nearby_wifi_devices" msgid="392774237063608500">"ಹತ್ತಿರದ ವೈ -ಫೈ ಸಾಧನಗಳ ಜೊತೆಗೆ ಸಂವಹನ ನಡೆಸಿ"</string>
<string name="permdesc_nearby_wifi_devices" msgid="3054307728646332906">"ಹತ್ತಿರದ ವೈ -ಫೈ ಸಾಧನಗಳ ಸಂಬಂಧಿತ ಸ್ಥಾನವನ್ನು ಸೂಚಿಸಲು, ಕನೆಕ್ಟ್ ಮಾಡಲು ಮತ್ತು ನಿರ್ಧರಿಸಲು ಆ್ಯಪ್‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ"</string>
<string name="permlab_preferredPaymentInfo" msgid="5274423844767445054">"ಆದ್ಯತೆಯ NFC ಪಾವತಿ ಸೇವಾ ಮಾಹಿತಿ"</string>
- <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"ನೋಂದಾಯಿತ ಅಪ್ಲಿಕೇಶನ್ ಗುರುತಿಸುವಿಕೆಗಳು ಮತ್ತು ಮಾರ್ಗ ಗಮ್ಯಸ್ಥಾನಗಳಂತಹ ಆದ್ಯತೆಯ NFC ಪಾವತಿ ಸೇವೆಗಳ ಬಗ್ಗೆ ಮಾಹಿತಿಯನ್ನು ಪಡೆಯಲು ಅಪ್ಲಿಕೇಶನ್‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ."</string>
+ <string name="permdesc_preferredPaymentInfo" msgid="8583552469807294967">"ನೋಂದಾಯಿತ ಆ್ಯಪ್ ಗುರುತಿಸುವಿಕೆಗಳು ಮತ್ತು ಮಾರ್ಗ ಗಮ್ಯಸ್ಥಾನಗಳಂತಹ ಆದ್ಯತೆಯ NFC ಪಾವತಿ ಸೇವೆಗಳ ಬಗ್ಗೆ ಮಾಹಿತಿಯನ್ನು ಪಡೆಯಲು ಆ್ಯಪ್‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ."</string>
<string name="permlab_nfc" msgid="1904455246837674977">"ಸಮೀಪ ಕ್ಷೇತ್ರ ಸಂವಹನವನ್ನು ನಿಯಂತ್ರಿಸಿ"</string>
- <string name="permdesc_nfc" msgid="8352737680695296741">"ಸಮೀಪದ ಕ್ಷೇತ್ರ ಸಂವಹನ (NFC) ಟ್ಯಾಗ್‌ಗಳು, ಕಾರ್ಡ್‌ಗಳು, ಮತ್ತು ಓದುಗರನ್ನು ಅಪ್ಲಿಕೇಶನ್‌ ಅನುಮತಿಸುತ್ತದೆ."</string>
+ <string name="permdesc_nfc" msgid="8352737680695296741">"ಸಮೀಪದ ಕ್ಷೇತ್ರ ಸಂವಹನ (NFC) ಟ್ಯಾಗ್‌ಗಳು, ಕಾರ್ಡ್‌ಗಳು, ಮತ್ತು ಓದುಗರನ್ನು ಆ್ಯಪ್‌ ಅನುಮತಿಸುತ್ತದೆ."</string>
<string name="permlab_nfcTransactionEvent" msgid="5868209446710407679">"ಸುರಕ್ಷಿತ ಅಂಶದ ವಹಿವಾಟು ಈವೆಂಟ್"</string>
<string name="permdesc_nfcTransactionEvent" msgid="1904286701876487397">"ಸುರಕ್ಷಿತ ಅಂಶದಲ್ಲಿ ನಡೆಯುತ್ತಿರುವ ವಹಿವಾಟುಗಳ ಕುರಿತು ಮಾಹಿತಿಯನ್ನು ಸ್ವೀಕರಿಸಲು ಆ್ಯಪ್‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ."</string>
<string name="permlab_disableKeyguard" msgid="3605253559020928505">"ನಿಮ್ಮ ಸ್ಕ್ರೀನ್ ಲಾಕ್ ಅನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಿ"</string>
@@ -757,7 +757,7 @@
<string name="face_error_vendor_unknown" msgid="7387005932083302070">"ಏನೋ ತಪ್ಪಾಗಿದೆ. ಪುನಃ ಪ್ರಯತ್ನಿಸಿ."</string>
<string name="face_icon_content_description" msgid="465030547475916280">"ಮುಖದ ಐಕಾನ್‌"</string>
<string name="permlab_readSyncSettings" msgid="6250532864893156277">"ಸಿಂಕ್ ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ರೀಡ್‌ ಮಾಡು"</string>
- <string name="permdesc_readSyncSettings" msgid="1325658466358779298">"ಒಂದು ಖಾತೆಯ ಸಿಂಕ್ ಸೆಟ್ಟಿಂಗ್‍‍ಗಳನ್ನು ಓದಲು ಅಪ್ಲಿಕೇಶನ್‍‍ಗೆ ಅವಕಾಶ ನೀಡುತ್ತದೆ. ಉದಾಹರಣೆಗೆ, ಖಾತೆಯೊಂದಿಗೆ ಜನರ ಅಪ್ಲಿಕೇಶನ್ ಸಿಂಕ್ ಮಾಡಲಾಗಿದೆಯೇ ಎಂಬುದನ್ನು ಇದು ನಿರ್ಧರಿಸಬಹುದು."</string>
+ <string name="permdesc_readSyncSettings" msgid="1325658466358779298">"ಒಂದು ಖಾತೆಯ ಸಿಂಕ್ ಸೆಟ್ಟಿಂಗ್‍‍ಗಳನ್ನು ಓದಲು ಆ್ಯಪ್‌ಗೆ ಅವಕಾಶ ನೀಡುತ್ತದೆ. ಉದಾಹರಣೆಗೆ, ಖಾತೆಯೊಂದಿಗೆ ಜನರ ಆ್ಯಪ್‌ ಸಿಂಕ್ ಮಾಡಲಾಗಿದೆಯೇ ಎಂಬುದನ್ನು ಇದು ನಿರ್ಧರಿಸಬಹುದು."</string>
<string name="permlab_writeSyncSettings" msgid="6583154300780427399">"ಸಿಂಕ್ ಆನ್ ಮತ್ತು ಸಿಂಕ್ ಆಫ್ ಟಾಗಲ್ ಮಾಡಿ"</string>
<string name="permdesc_writeSyncSettings" msgid="6029151549667182687">"ಖಾತೆಗೆ ಸಿಂಕ್ ಸೆಟ್ಟಿಂಗ್‍‍ಗಳನ್ನು ಮಾರ್ಪಡಿಸಲು ಅಪ್ಲಿಕೇಶನ್‍‍ಗೆ ಅನುಮತಿಸುತ್ತದೆ. ಉದಾಹರಣೆಗೆ, ಖಾತೆಯನ್ನು ಹೊಂದಿರುವ ವ್ಯಕ್ತಿಗಳ ಸಿಂಕ್ ಸಕ್ರಿಯಗೊಳಿಸಲು ಇದನ್ನು ಬಳಸಬಹುದಾಗಿದೆ."</string>
<string name="permlab_readSyncStats" msgid="3747407238320105332">"ಸಿಂಕ್ ಅಂಕಿಅಂಶಗಳನ್ನು ಓದಿರಿ"</string>
@@ -779,19 +779,19 @@
<string name="permlab_register_sim_subscription" msgid="1653054249287576161">"ಹೊಸ ಟೆಲಿಕಾಮ್ ಸಿಮ್‌ ಸಂಪರ್ಕಗಳನ್ನು ನೋಂದಾಯಿಸಿ"</string>
<string name="permdesc_register_sim_subscription" msgid="4183858662792232464">"ಅಪ್ಲಿಕೇಶನ್‌ಗೆ ಹೊಸ ಟೆಲಿಕಾಮ್ ಸಿಮ್‌ ಸಂಪರ್ಕಗಳನ್ನು ನೋಂದಾಯಿಸಲು ಅನುಮತಿಸುತ್ತದೆ."</string>
<string name="permlab_register_call_provider" msgid="6135073566140050702">"ಹೊಸ ಟೆಲಿಕಾಮ್ ಸಂಪರ್ಕಗಳನ್ನು ನೋಂದಾಯಿಸಿ"</string>
- <string name="permdesc_register_call_provider" msgid="4201429251459068613">"ಹೊಸ ಟೆಲಿಕಾಂ ಸಂಪರ್ಕಗಳನ್ನು ನೋಂದಣಿ ಮಾಡಲು ಅಪ್ಲಿಕೇಶನ್ ಅವಕಾಶ ಮಾಡಿಕೊಡುತ್ತದೆ."</string>
+ <string name="permdesc_register_call_provider" msgid="4201429251459068613">"ಹೊಸ ಟೆಲಿಕಾಂ ಸಂಪರ್ಕಗಳನ್ನು ನೋಂದಣಿ ಮಾಡಲು ಆ್ಯಪ್‌ ಅವಕಾಶ ಮಾಡಿಕೊಡುತ್ತದೆ."</string>
<string name="permlab_connection_manager" msgid="3179365584691166915">"ಟೆಲಿಕಾಂ ಸಂಪರ್ಕಗಳನ್ನು ನಿರ್ವಹಿಸಿ"</string>
- <string name="permdesc_connection_manager" msgid="1426093604238937733">"ಟೆಲಿಕಾಂ ಸಂಪರ್ಕಗಳನ್ನು ನಿರ್ವಹಿಸಲು ಅಪ್ಲಿಕೇಶನ್ ಅವಕಾಶ ಮಾಡಿಕೊಡುತ್ತದೆ."</string>
+ <string name="permdesc_connection_manager" msgid="1426093604238937733">"ಟೆಲಿಕಾಂ ಸಂಪರ್ಕಗಳನ್ನು ನಿರ್ವಹಿಸಲು ಆ್ಯಪ್‌ ಅವಕಾಶ ಮಾಡಿಕೊಡುತ್ತದೆ."</string>
<string name="permlab_bind_incall_service" msgid="5990625112603493016">"ಒಳ-ಕಾಲ್ ಸ್ಕ್ರೀನ್ ಮೂಲಕ ಸಂವಹನ ನಡೆಸಿ"</string>
<string name="permdesc_bind_incall_service" msgid="4124917526967765162">"ಬಳಕೆದಾರರು ಒಳ-ಕಾಲ್ ಸ್ಕ್ರೀನ್ ಅನ್ನು ಯಾವಾಗ ಮತ್ತು ಹೇಗೆ ನೋಡುತ್ತಾರೆ ಎಂಬುದನ್ನು ನಿಯಂತ್ರಿಸಲು ಅಪ್ಲಿಕೇಶನ್‌ಗೆ ಅವಕಾಶ ಮಾಡಿಕೊಡುತ್ತದೆ."</string>
<string name="permlab_bind_connection_service" msgid="5409268245525024736">"ಟೆಲಿಫೋನಿ ಸೇವೆಗಳೊಂದಿಗೆ ಸಂವಾದ ನಡೆಸಿ"</string>
<string name="permdesc_bind_connection_service" msgid="6261796725253264518">"ಕರೆಗಳನ್ನು ಮಾಡಲು/ಸ್ವೀಕರಿಸುವ ನಿಟ್ಟಿನಲ್ಲಿ ಲಿಫೋನಿ ಸೇವೆಗಳ ಜೊತೆ ಸಂವಾದ ನಡೆಸಲು ಅಪ್ಲಿಕೇಶನ್‌ಗೆ ಅವಕಾಶ ಮಾಡಕೊಡಿ."</string>
<string name="permlab_control_incall_experience" msgid="6436863486094352987">"ಒಳ ಕರೆ ಬಳಕೆದಾರರ ಅನುಭವವನ್ನು ಒದಗಿಸಿ"</string>
- <string name="permdesc_control_incall_experience" msgid="5896723643771737534">"ಒಳ ಕರೆಯ ಬಳಕೆದಾರರ ಅನುಭವವನ್ನು ಒದಗಿಸಲು ಅಪ್ಲಿಕೇಶನ್‌ ಅನುಮತಿಸುತ್ತದೆ."</string>
+ <string name="permdesc_control_incall_experience" msgid="5896723643771737534">"ಒಳ ಕರೆಯ ಬಳಕೆದಾರರ ಅನುಭವವನ್ನು ಒದಗಿಸಲು ಆ್ಯಪ್‌ ಅನುಮತಿಸುತ್ತದೆ."</string>
<string name="permlab_readNetworkUsageHistory" msgid="8470402862501573795">"ಐತಿಹಾಸಿಕ ನೆಟ್‌ವರ್ಕ್ ಬಳಕೆಯನ್ನು ಓದಿರಿ"</string>
<string name="permdesc_readNetworkUsageHistory" msgid="1112962304941637102">"ನಿರ್ದಿಷ್ಟ ನೆಟ್‌ವರ್ಕ್‌ಗಳು ಮತ್ತು ಅಪ್ಲಿಕೇಶನ್‌ಗಳಿಗೆ ಐತಿಹಾಸಿಕ ನೆಟ್‌ವರ್ಕ್‌ನ ಬಳಕೆಯನ್ನು ಓದಲು ಅಪ್ಲಿಕೇಶನ್‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ."</string>
<string name="permlab_manageNetworkPolicy" msgid="6872549423152175378">"ನೆಟ್‌ವರ್ಕ್ ನೀತಿಯನ್ನು ನಿರ್ವಹಿಸಿ"</string>
- <string name="permdesc_manageNetworkPolicy" msgid="1865663268764673296">"ನೆಟ್‌‌ವರ್ಕ್‌ ನೀತಿಗಳನ್ನು ನಿರ್ವಹಿಸಲು ಮತ್ತು ಅಪ್ಲಿಕೇಶನ್ ನಿರ್ದಿಷ್ಟ ನಿಯಮಗಳನ್ನು ವ್ಯಾಖ್ಯಾನಿಸಲು ಅಪ್ಲಿಕೇಶನ್‌‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ."</string>
+ <string name="permdesc_manageNetworkPolicy" msgid="1865663268764673296">"ನೆಟ್‌‌ವರ್ಕ್‌ ನೀತಿಗಳನ್ನು ನಿರ್ವಹಿಸಲು ಮತ್ತು ಆ್ಯಪ್‌ ನಿರ್ದಿಷ್ಟ ನಿಯಮಗಳನ್ನು ವ್ಯಾಖ್ಯಾನಿಸಲು ಆ್ಯಪ್‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ."</string>
<string name="permlab_modifyNetworkAccounting" msgid="7448790834938749041">"ನೆಟವರ್ಕ್ ಬಳಕೆಯ ಲೆಕ್ಕ ಪರಿಶೋಧನೆಯನ್ನು ಮಾರ್ಪಡಿಸಿ"</string>
<string name="permdesc_modifyNetworkAccounting" msgid="5076042642247205390">"ಅಪ್ಲಿಕೇಶನ್‍‍ಗಳಿಗೆ ವಿರುದ್ಧವಾಗಿ ನೆಟ್‍‍ವರ್ಕ್ ಬಳಕೆಯನ್ನು ಹೇಗೆ ಲೆಕ್ಕಿಸಲಾಗಿದೆ ಎಂಬುದನ್ನು ಮಾರ್ಪಡಿಸಲು ಅಪ್ಲಿಕೇಶನ್‍‍ಗೆ ಅವಕಾಶ ಮಾಡಿಕೊಡುತ್ತದೆ. ಸಾಮಾನ್ಯ ಅಪ್ಲಿಕೇಶನ್‍‍ಗಳಲ್ಲಿ ಬಳಸಲಾಗುವುದಿಲ್ಲ."</string>
<string name="permlab_accessNotifications" msgid="7130360248191984741">"ಅಧಿಸೂಚನೆಗಳನ್ನು ಪ್ರವೇಶಿಸಿ"</string>
@@ -803,7 +803,7 @@
<string name="permlab_bindDreamService" msgid="4776175992848982706">"ಕನಸಿನ ಸೇವೆಗೆ ಪ್ರತಿಬಂಧಿಸಿ"</string>
<string name="permdesc_bindDreamService" msgid="9129615743300572973">"ಕನಸಿನ ಸೇವೆಯ ಮೇಲ್ಮಟ್ಟದ ಇಂಟರ್ಫೇಸ್‌ಗೆ ಪ್ರತಿಬಂಧಿಸಲು ಮಾಲೀಕರಿಗೆ ಅವಕಾಶ ನೀಡುತ್ತದೆ. ಸಾಮಾನ್ಯ ಅಪ್ಲಿಕೇಶನ್‌ಗಳಿಗೆ ಎಂದಿಗೂ ಅಗತ್ಯವಿರುವುದಿಲ್ಲ."</string>
<string name="permlab_invokeCarrierSetup" msgid="5098810760209818140">"ವಾಹಕ-ಒದಗಿಸಿರುವ ಕಾನ್ಫಿಗರೇಶನ್ ಅಪ್ಲಿಕೇಶನ್‌ಗೆ ವಿನಂತಿಸಿಕೊಳ್ಳಿ"</string>
- <string name="permdesc_invokeCarrierSetup" msgid="4790845896063237887">"ವಾಹಕ-ಒದಗಿಸಿರುವ ಕಾನ್ಫಿಗರೇಶನ್ ಅಪ್ಲಿಕೇಶನ್ ಅನ್ನು ವಿನಂತಿಸಲು ಹೊಂದಿರುವವರಿಗೆ ಅವಕಾಶ ಮಾಡಿಕೊಡುತ್ತದೆ. ಸಾಮಾನ್ಯ ಅಪ್ಲಿಕೇಶನ್‌ಗಳಿಗಾಗಿ ಎಂದಿಗೂ ಅಗತ್ಯವಿರುವುದಿಲ್ಲ."</string>
+ <string name="permdesc_invokeCarrierSetup" msgid="4790845896063237887">"ವಾಹಕ-ಒದಗಿಸಿರುವ ಕಾನ್ಫಿಗರೇಶನ್ ಆ್ಯಪ್‌ ಅನ್ನು ವಿನಂತಿಸಲು ಹೊಂದಿರುವವರಿಗೆ ಅವಕಾಶ ಮಾಡಿಕೊಡುತ್ತದೆ. ಸಾಮಾನ್ಯ ಆ್ಯಪ್‌ಗಳಿಗಾಗಿ ಎಂದಿಗೂ ಅಗತ್ಯವಿರುವುದಿಲ್ಲ."</string>
<string name="permlab_accessNetworkConditions" msgid="1270732533356286514">"ನೆಟ್‌ವರ್ಕ್ ಪರಿಸ್ಥಿತಿಗಳ ಕುರಿತು ಪರಿಶೀಲನೆಗಳನ್ನು ಆಲಿಸಿ"</string>
<string name="permdesc_accessNetworkConditions" msgid="2959269186741956109">"ನೆಟ್‌ವರ್ಕ್ ಪರಿಸ್ಥಿತಿಗಳ ಕುರಿತು ಪರಿಶೀಲನೆಗಾಗಿ ಆಲಿಸಲು ಅಪ್ಲಿಕೇಶನ್‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ. ಸಾಮಾನ್ಯ ಅಪ್ಲಿಕೇಶನ್‌ಗಳಿಗಾಗಿ ಎಂದಿಗೂ ಅಗತ್ಯವಿರುವುದಿಲ್ಲ."</string>
<string name="permlab_setInputCalibration" msgid="932069700285223434">"ಇನ್‌ಪುಟ್‌‌ ಸಾಧನ ಮಾಪನಾಂಕ ನಿರ್ಣಯವನ್ನು ಬದಲಾಯಿಸಿ"</string>
@@ -863,7 +863,7 @@
<string name="policylab_expirePassword" msgid="6015404400532459169">"ಸ್ಕ್ರೀನ್ ಲಾಕ್ ಪಾಸ್‌ವರ್ಡ್ ಮುಕ್ತಾಯವನ್ನು ಹೊಂದಿಸಿ"</string>
<string name="policydesc_expirePassword" msgid="9136524319325960675">"ಸ್ಕ್ರೀನ್ ಲಾಕ್ ಪಾಸ್‌ವರ್ಡ್, ಪಿನ್, ಅಥವಾ ನಮೂನೆಯನ್ನು ಹೆಚ್ಚು ಪದೆ ಪದೇ ಬದಲಾಯಿಸಬೇಕಾಗಿರುತ್ತದೆ ಎಂಬುದನ್ನು ಬದಲಾಯಿಸಿ."</string>
<string name="policylab_encryptedStorage" msgid="9012936958126670110">"ಸಂಗ್ರಹಣೆ ಎನ್‌ಕ್ರಿಪ್ಶನ್ ಹೊಂದಿಸಿ"</string>
- <string name="policydesc_encryptedStorage" msgid="1102516950740375617">"ಸಂಗ್ರಹಿಸಿರುವ ಅಪ್ಲಿಕೇಶನ್ ಡೇಟಾವನ್ನು ಎನ್‌ಕ್ರಿಪ್ಟ್ ಮಾಡಬೇಕಾದ ಅಗತ್ಯವಿದೆ."</string>
+ <string name="policydesc_encryptedStorage" msgid="1102516950740375617">"ಸಂಗ್ರಹಿಸಿರುವ ಆ್ಯಪ್ ಡೇಟಾವನ್ನು ಎನ್‌ಕ್ರಿಪ್ಟ್ ಮಾಡಬೇಕಾದ ಅಗತ್ಯವಿದೆ."</string>
<string name="policylab_disableCamera" msgid="5749486347810162018">"ಕ್ಯಾಮರಾಗಳನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಿ"</string>
<string name="policydesc_disableCamera" msgid="3204405908799676104">"ಎಲ್ಲಾ ಸಾಧನ ಕ್ಯಾಮರಾಗಳ ಬಳಕೆಯನ್ನು ತಡೆಯಿರಿ."</string>
<string name="policylab_disableKeyguardFeatures" msgid="5071855750149949741">"ಕೆಲವು ಸ್ಕ್ರೀನ್ ಲಾಕ್ ವೈಶಿಷ್ಟ್ಯಗಳನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಿ"</string>
@@ -1200,7 +1200,7 @@
<string name="low_internal_storage_view_text" msgid="8172166728369697835">"ಕೆಲವು ಸಿಸ್ಟಂ ಕಾರ್ಯವಿಧಾನಗಳು ಕಾರ್ಯನಿರ್ವಹಿಸದೇ ಇರಬಹುದು"</string>
<string name="low_internal_storage_view_text_no_boot" msgid="7368968163411251788">"ಸಿಸ್ಟಂನಲ್ಲಿ ಸಾಕಷ್ಟು ಸಂಗ್ರಹಣೆಯಿಲ್ಲ. ನೀವು 250MB ನಷ್ಟು ಖಾಲಿ ಸ್ಥಳವನ್ನು ಹೊಂದಿರುವಿರಾ ಎಂಬುದನ್ನು ಖಚಿತಪಡಿಸಿಕೊಳ್ಳಿ ಹಾಗೂ ಮರುಪ್ರಾರಂಭಿಸಿ."</string>
<string name="app_running_notification_title" msgid="8985999749231486569">"<xliff:g id="APP_NAME">%1$s</xliff:g> ಚಾಲನೆಯಲ್ಲಿದೆ"</string>
- <string name="app_running_notification_text" msgid="5120815883400228566">"ಹೆಚ್ಚಿನ ಮಾಹಿತಿಗಾಗಿ ಅಥವಾ ಅಪ್ಲಿಕೇಶನ್ ನಿಲ್ಲಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ."</string>
+ <string name="app_running_notification_text" msgid="5120815883400228566">"ಹೆಚ್ಚಿನ ಮಾಹಿತಿಗಾಗಿ ಅಥವಾ ಆ್ಯಪ್‌ ನಿಲ್ಲಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ."</string>
<string name="ok" msgid="2646370155170753815">"ಸರಿ"</string>
<string name="cancel" msgid="6908697720451760115">"ರದ್ದುಮಾಡಿ"</string>
<string name="yes" msgid="9069828999585032361">"ಸರಿ"</string>
@@ -1235,7 +1235,7 @@
<string name="whichSendToApplication" msgid="77101541959464018">"ಇದನ್ನು ಬಳಸಿಕೊಂಡು ಕಳುಹಿಸಿ"</string>
<string name="whichSendToApplicationNamed" msgid="3385686512014670003">"%1$s ಬಳಸಿಕೊಂಡು ಕಳುಹಿಸಿ"</string>
<string name="whichSendToApplicationLabel" msgid="3543240188816513303">"ಕಳುಹಿಸು"</string>
- <string name="whichHomeApplication" msgid="8276350727038396616">"ಮುಖಪುಟ‌ ಅಪ್ಲಿಕೇಶನ್‌ ಆಯ್ಕೆಮಾಡಿ"</string>
+ <string name="whichHomeApplication" msgid="8276350727038396616">"ಮುಖಪುಟ‌ ಆ್ಯಪ್‌ ಆಯ್ಕೆಮಾಡಿ"</string>
<string name="whichHomeApplicationNamed" msgid="5855990024847433794">"%1$s ಅನ್ನು ಹೋಮ್ ಆಗಿ ಬಳಸಿ"</string>
<string name="whichHomeApplicationLabel" msgid="8907334282202933959">"ಚಿತ್ರ ಕ್ಯಾಪ್ಚರ್ ಮಾಡಿ"</string>
<string name="whichImageCaptureApplication" msgid="2737413019463215284">"ಇದರ ಜೊತೆಗೆ ಚಿತ್ರ ಕ್ಯಾಪ್ಚರ್ ಮಾಡಿ"</string>
@@ -1251,12 +1251,12 @@
<string name="aerr_process" msgid="4268018696970966407">"<xliff:g id="PROCESS">%1$s</xliff:g> ನಿಲ್ಲಿಸಿದೆ"</string>
<string name="aerr_application_repeated" msgid="7804378743218496566">"<xliff:g id="APPLICATION">%1$s</xliff:g> ನಿಲ್ಲುತ್ತಲೇ ಇರುತ್ತದೆ"</string>
<string name="aerr_process_repeated" msgid="1153152413537954974">"<xliff:g id="PROCESS">%1$s</xliff:g> ನಿಲ್ಲುತ್ತಲೇ ಇರುತ್ತದೆ"</string>
- <string name="aerr_restart" msgid="2789618625210505419">"ಅಪ್ಲಿಕೇಶನ್ ಮತ್ತೆ ತೆರೆಯಿರಿ"</string>
+ <string name="aerr_restart" msgid="2789618625210505419">"ಆ್ಯಪ್ ಮತ್ತೆ ತೆರೆಯಿರಿ"</string>
<string name="aerr_report" msgid="3095644466849299308">"ಪ್ರತಿಕ್ರಿಯೆ ಕಳುಹಿಸಿ"</string>
<string name="aerr_close" msgid="3398336821267021852">"ಮುಚ್ಚಿ"</string>
<string name="aerr_mute" msgid="2304972923480211376">"ಸಾಧನವು ಮರುಪ್ರಾರಂಭವಾಗುವವರೆಗೂ ಮ್ಯೂಟ್ ಮಾಡಿ"</string>
<string name="aerr_wait" msgid="3198677780474548217">"ನಿರೀಕ್ಷಿಸು"</string>
- <string name="aerr_close_app" msgid="8318883106083050970">"ಅಪ್ಲಿಕೇಶನ್ ಅನ್ನು ಮುಚ್ಚಿ"</string>
+ <string name="aerr_close_app" msgid="8318883106083050970">"ಆ್ಯಪ್‌ ಅನ್ನು ಮುಚ್ಚಿ"</string>
<string name="anr_title" msgid="7290329487067300120"></string>
<string name="anr_activity_application" msgid="8121716632960340680">"<xliff:g id="APPLICATION">%2$s</xliff:g> ಪ್ರತಿಕ್ರಿಯಿಸುತ್ತಿಲ್ಲ"</string>
<string name="anr_activity_process" msgid="3477362583767128667">"<xliff:g id="ACTIVITY">%1$s</xliff:g> ಪ್ರತಿಕ್ರಿಯಿಸುತ್ತಿಲ್ಲ"</string>
@@ -1266,7 +1266,7 @@
<string name="report" msgid="2149194372340349521">"ವರದಿ ಮಾಡು"</string>
<string name="wait" msgid="7765985809494033348">"ನಿರೀಕ್ಷಿಸು"</string>
<string name="webpage_unresponsive" msgid="7850879412195273433">"ಪುಟವು ಪ್ರತಿಕ್ರಿಯೆ ರಹಿತವಾಗಿದೆ.\n\nನೀವದನ್ನು ಮುಚ್ಚಲು ಬಯಸುವಿರಾ?"</string>
- <string name="launch_warning_title" msgid="6725456009564953595">"ಅಪ್ಲಿಕೇಶನ್‌ ಮರುನಿರ್ದೇಶಿಸಲಾಗಿದೆ"</string>
+ <string name="launch_warning_title" msgid="6725456009564953595">"ಆ್ಯಪ್‌ ಮರುನಿರ್ದೇಶಿಸಲಾಗಿದೆ"</string>
<string name="launch_warning_replace" msgid="3073392976283203402">"ಇದೀಗ <xliff:g id="APP_NAME">%1$s</xliff:g> ರನ್ ಆಗುತ್ತಿದೆ."</string>
<string name="launch_warning_original" msgid="3332206576800169626">"<xliff:g id="APP_NAME">%1$s</xliff:g> ಅನ್ನು ಮೂಲತಃ ಲಾಂಚ್ ಮಾಡಲಾಗಿದೆ."</string>
<string name="screen_compat_mode_scale" msgid="8627359598437527726">"ಮಾಪಕ"</string>
@@ -1277,7 +1277,7 @@
<string name="unsupported_compile_sdk_message" msgid="7326293500707890537">"Android OS ನ ಹೊಂದಾಣಿಕೆಯಾಗದ ಆವೃತ್ತಿ ಗಾಗಿ <xliff:g id="APP_NAME">%1$s</xliff:g> ಅನ್ನು ರಚಿಸಲಾಗಿದೆ ಮತ್ತು ಅನಿರೀಕ್ಷಿತವಾಗಿ ವರ್ತಿಸಬಹುದು. ಅಪ್ಲಿಕೇಶನ್‌ನ ಅಪ್‌ಡೇಟ್ ಮಾಡಲಾದ ಆವೃತ್ತಿ ಲಭ್ಯವಿರಬಹುದು."</string>
<string name="unsupported_compile_sdk_show" msgid="1601210057960312248">"ಯಾವಾಗಲೂ ತೋರಿಸು"</string>
<string name="unsupported_compile_sdk_check_update" msgid="1103639989147664456">"ಅಪ್‌ಡೇಟ್‌ಗಾಗಿ ಪರಿಶೀಲಿಸಿ"</string>
- <string name="smv_application" msgid="3775183542777792638">"ಅಪ್ಲಿಕೇಶನ್‌‌ <xliff:g id="APPLICATION">%1$s</xliff:g> (ಪ್ರಕ್ರಿಯೆಯು <xliff:g id="PROCESS">%2$s</xliff:g>) ತನ್ನ ಸ್ವಯಂ-ಜಾರಿ ಕಠಿಣ ಮೋಡ್ ನೀತಿಯನ್ನು ಉಲ್ಲಂಘನೆ ಮಾಡಿದೆ."</string>
+ <string name="smv_application" msgid="3775183542777792638">"ಆ್ಯಪ್ <xliff:g id="APPLICATION">%1$s</xliff:g> (ಪ್ರಕ್ರಿಯೆಯು <xliff:g id="PROCESS">%2$s</xliff:g>) ತನ್ನ ಸ್ವಯಂ-ಜಾರಿ ಕಠಿಣ ಮೋಡ್ ನೀತಿಯನ್ನು ಉಲ್ಲಂಘನೆ ಮಾಡಿದೆ."</string>
<string name="smv_process" msgid="1398801497130695446">"<xliff:g id="PROCESS">%1$s</xliff:g> ಪ್ರಕ್ರಿಯೆಯು ತನ್ನ ಸ್ವಯಂ-ಜಾರಿ ಕಠಿಣ ಮೋಡ್ ನೀತಿಯನ್ನು ಉಲ್ಲಂಘನೆ ಮಾಡಿದೆ."</string>
<string name="android_upgrading_title" product="default" msgid="7279077384220829683">"ಫೋನ್ ಅನ್ನು ಅಪ್‌ಡೇಟ್ ಮಾಡಲಾಗುತ್ತಿದೆ…"</string>
<string name="android_upgrading_title" product="tablet" msgid="4268417249079938805">"ಟ್ಯಾಬ್ಲೆಟ್ ಅನ್ನು ಅಪ್‌ಡೇಟ್ ಮಾಡಲಾಗುತ್ತಿದೆ…"</string>
@@ -1379,9 +1379,9 @@
<string name="sim_added_message" msgid="6602906609509958680">"ಮೊಬೈಲ್ ನೆಟ್‍ವರ್ಕ್ ಪ್ರವೇಶಿಸಲು ನಿಮ್ಮ ಸಾಧನವನ್ನು ಮರುಪ್ರಾರಂಭಿಸಿ."</string>
<string name="sim_restart_button" msgid="8481803851341190038">"ಮರುಪ್ರಾರಂಭಿಸು"</string>
<string name="install_carrier_app_notification_title" msgid="5712723402213090102">"ಮೊಬೈಲ್ ಸೇವೆಯನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿ"</string>
- <string name="install_carrier_app_notification_text" msgid="2781317581274192728">"ನಿಮ್ಮ ಹೊಸ ಸಿಮ್ ಅನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಲು ವಾಹಕ ಅಪ್ಲಿಕೇಶನ್ ಅನ್ನು ಬಳಸಿ"</string>
- <string name="install_carrier_app_notification_text_app_name" msgid="4086877327264106484">"ನಿಮ್ಮ ಹೊಸ ಸಿಮ್ ಅನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಲು <xliff:g id="APP_NAME">%1$s</xliff:g> ಅಪ್ಲಿಕೇಶನ್‌ ಅನ್ನು ಡೌನ್‌ಲೋಡ್ ಮಾಡಿ"</string>
- <string name="install_carrier_app_notification_button" msgid="6257740533102594290">"ಅಪ್ಲಿಕೇಶನ್ ಡೌನ್‌ಲೋಡ್ ಮಾಡಿ"</string>
+ <string name="install_carrier_app_notification_text" msgid="2781317581274192728">"ನಿಮ್ಮ ಹೊಸ ಸಿಮ್ ಅನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಲು ವಾಹಕ ಆ್ಯಪ್ ಅನ್ನು ಬಳಸಿ"</string>
+ <string name="install_carrier_app_notification_text_app_name" msgid="4086877327264106484">"ನಿಮ್ಮ ಹೊಸ ಸಿಮ್ ಅನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಲು <xliff:g id="APP_NAME">%1$s</xliff:g> ಆ್ಯಪ್‌ ಅನ್ನು ಡೌನ್‌ಲೋಡ್ ಮಾಡಿ"</string>
+ <string name="install_carrier_app_notification_button" msgid="6257740533102594290">"ಆ್ಯಪ್‌ ಡೌನ್‌ಲೋಡ್ ಮಾಡಿ"</string>
<string name="carrier_app_notification_title" msgid="5815477368072060250">"ಹೊಸ ಸಿಮ್ ಸೇರಿಸಲಾಗಿದೆ"</string>
<string name="carrier_app_notification_text" msgid="6567057546341958637">"ಇದನ್ನು ಸ್ಥಾಪಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
<string name="time_picker_dialog_title" msgid="9053376764985220821">"ಸಮಯವನ್ನು ಹೊಂದಿಸಿ"</string>
@@ -1502,7 +1502,7 @@
<string name="permlab_requestDeletePackages" msgid="2541172829260106795">"ಪ್ಯಾಕೇಜ್‌ಗಳನ್ನು ಅಳಿಸಲು ವಿನಂತಿ"</string>
<string name="permdesc_requestDeletePackages" msgid="6133633516423860381">"ಪ್ಯಾಕೇಜ್‌ಗಳನ್ನು ಅಳಿಸುವುದಕ್ಕಾಗಿ ವಿನಂತಿ ಮಾಡಲು ಅಪ್ಲಿಕೇಶನ್‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ."</string>
<string name="permlab_requestIgnoreBatteryOptimizations" msgid="7646611326036631439">"ಬ್ಯಾಟರಿ ಆಪ್ಟಿಮೈಸೇಶನ್‌ಗಳನ್ನು ಕಡೆಗಣಿಸಲು ಕೇಳಿ"</string>
- <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"ಈ ಅಪ್ಲಿಕೇಶನ್‌ಗೆ ಬ್ಯಾಟರಿ ಆಪ್ಟಿಮೈಸೇಶನ್‌ಗಳನ್ನು ಕಡೆಗಣಿಸುವುದಕ್ಕೆ ಅನುಮತಿಯನ್ನು ಕೇಳಲು ಅಪ್ಲಿಕೇಶನ್‌ ಅನ್ನು ಅನುಮತಿಸುತ್ತದೆ."</string>
+ <string name="permdesc_requestIgnoreBatteryOptimizations" msgid="634260656917874356">"ಈ ಆ್ಯಪ್‌ಗೆ ಬ್ಯಾಟರಿ ಆಪ್ಟಿಮೈಸೇಶನ್‌ಗಳನ್ನು ಕಡೆಗಣಿಸುವುದಕ್ಕೆ ಅನುಮತಿಯನ್ನು ಕೇಳಲು ಆ್ಯಪ್‌ ಅನ್ನು ಅನುಮತಿಸುತ್ತದೆ."</string>
<string name="permlab_queryAllPackages" msgid="2928450604653281650">"ಎಲ್ಲಾ ಪ್ಯಾಕೇಜ್‌ಗಳ ಕುರಿತಾದ ಮಾಹಿತಿಯನ್ನು ಕೇಳಿ"</string>
<string name="permdesc_queryAllPackages" msgid="5339069855520996010">"ಇನ್‌ಸ್ಟಾಲ್ ಮಾಡಿದ ಎಲ್ಲಾ ಪ್ಯಾಕೇಜ್‌ಗಳನ್ನು ವೀಕ್ಷಿಸಲು ಆ್ಯಪ್‌ಗೆ ಅನುಮತಿಸುತ್ತದೆ."</string>
<string name="tutorial_double_tap_to_zoom_message_short" msgid="1842872462124648678">"ಝೂಮ್‌ ನಿಯಂತ್ರಿಸಲು ಎರಡು ಬಾರಿ ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
@@ -1524,8 +1524,8 @@
<string name="permission_request_notification_title" msgid="1810025922441048273">"ಅನುಮತಿ ವಿನಂತಿಸಲಾಗಿದೆ"</string>
<string name="permission_request_notification_with_subtitle" msgid="3743417870360129298">"<xliff:g id="ACCOUNT">%s</xliff:g> ಖಾತೆಗಾಗಿ\n ಅನುಮತಿಯನ್ನು ವಿನಂತಿಸಲಾಗಿದೆ."</string>
<string name="permission_request_notification_for_app_with_subtitle" msgid="1298704005732851350">"<xliff:g id="ACCOUNT">%2$s</xliff:g> ಖಾತೆಗಾಗಿ \n <xliff:g id="APP">%1$s</xliff:g> ನಿಂದ ಅನುಮತಿಯನ್ನು ವಿನಂತಿಸಲಾಗಿದೆ."</string>
- <string name="forward_intent_to_owner" msgid="4620359037192871015">"ನಿಮ್ಮ ಕೆಲಸದ ಪ್ರೊಫೈಲ್‌ನ ಹೊರಗೆ ನೀವು ಈ ಅಪ್ಲಿಕೇಶನ್‌ ಅನ್ನು ಬಳಸುತ್ತಿರುವಿರಿ"</string>
- <string name="forward_intent_to_work" msgid="3620262405636021151">"ನಿಮ್ಮ ಕೆಲಸದ ಪ್ರೊಫೈಲ್‌ನಲ್ಲಿ ನೀವು ಈ ಅಪ್ಲಿಕೇಶನ್‌ ಅನ್ನು ಬಳಸುತ್ತಿರುವಿರಿ"</string>
+ <string name="forward_intent_to_owner" msgid="4620359037192871015">"ನಿಮ್ಮ ಕೆಲಸದ ಪ್ರೊಫೈಲ್‌ನ ಹೊರಗೆ ನೀವು ಈ ಆ್ಯಪ್‌ ಅನ್ನು ಬಳಸುತ್ತಿರುವಿರಿ"</string>
+ <string name="forward_intent_to_work" msgid="3620262405636021151">"ನಿಮ್ಮ ಕೆಲಸದ ಪ್ರೊಫೈಲ್‌ನಲ್ಲಿ ನೀವು ಈ ಆ್ಯಪ್ ಅನ್ನು ಬಳಸುತ್ತಿರುವಿರಿ"</string>
<string name="input_method_binding_label" msgid="1166731601721983656">"ಇನ್‌ಪುಟ್ ವಿಧಾನ"</string>
<string name="sync_binding_label" msgid="469249309424662147">"ಸಿಂಕ್ ಮಾಡು"</string>
<string name="accessibility_binding_label" msgid="1974602776545801715">"ಆ್ಯಕ್ಸೆಸಿಬಿಲಿಟಿ"</string>
@@ -1548,8 +1548,8 @@
<string name="no_file_chosen" msgid="4146295695162318057">"ಯಾವುದೇ ಫೈಲ್ ಆಯ್ಕೆ ಮಾಡಿಲ್ಲ"</string>
<string name="reset" msgid="3865826612628171429">"ಮರುಹೊಂದಿಸು"</string>
<string name="submit" msgid="862795280643405865">"ಸಲ್ಲಿಸು"</string>
- <string name="car_mode_disable_notification_title" msgid="8450693275833142896">"ಡ್ರೈವಿಂಗ್‌ ಅಪ್ಲಿಕೇಶನ್ ಚಾಲನೆಯಲ್ಲಿದೆ"</string>
- <string name="car_mode_disable_notification_message" msgid="8954550232288567515">"ಡ್ರೈವಿಂಗ್‌ ಅಪ್ಲಿಕೇಶನ್ ನಿರ್ಗಮಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ."</string>
+ <string name="car_mode_disable_notification_title" msgid="8450693275833142896">"ಡ್ರೈವಿಂಗ್‌ ಆ್ಯಪ್‌ ಚಾಲನೆಯಲ್ಲಿದೆ"</string>
+ <string name="car_mode_disable_notification_message" msgid="8954550232288567515">"ಡ್ರೈವಿಂಗ್‌ ಆ್ಯಪ್ ನಿರ್ಗಮಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ."</string>
<string name="back_button_label" msgid="4078224038025043387">"ಹಿಂದೆ"</string>
<string name="next_button_label" msgid="6040209156399907780">"ಮುಂದಿನದು"</string>
<string name="skip_button_label" msgid="3566599811326688389">"ಸ್ಕಿಪ್‌"</string>
@@ -2000,8 +2000,8 @@
<string name="language_picker_section_all" msgid="1985809075777564284">"ಎಲ್ಲಾ ಭಾಷೆಗಳು"</string>
<string name="region_picker_section_all" msgid="756441309928774155">"ಎಲ್ಲಾ ಪ್ರದೇಶಗಳು"</string>
<string name="locale_search_menu" msgid="6258090710176422934">"ಹುಡುಕಿ"</string>
- <string name="app_suspended_title" msgid="888873445010322650">"ಅಪ್ಲಿಕೇಶನ್ ಲಭ್ಯವಿಲ್ಲ"</string>
- <string name="app_suspended_default_message" msgid="6451215678552004172">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> ಅಪ್ಲಿಕೇಶನ್‌ ಸದ್ಯಕ್ಕೆ ಲಭ್ಯವಿಲ್ಲ. ಇದನ್ನು <xliff:g id="APP_NAME_1">%2$s</xliff:g> ನಲ್ಲಿ ನಿರ್ವಹಿಸಲಾಗುತ್ತಿದೆ."</string>
+ <string name="app_suspended_title" msgid="888873445010322650">"ಆ್ಯಪ್ ಲಭ್ಯವಿಲ್ಲ"</string>
+ <string name="app_suspended_default_message" msgid="6451215678552004172">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> ಆ್ಯಪ್‌ ಸದ್ಯಕ್ಕೆ ಲಭ್ಯವಿಲ್ಲ. ಇದನ್ನು <xliff:g id="APP_NAME_1">%2$s</xliff:g> ನಲ್ಲಿ ನಿರ್ವಹಿಸಲಾಗುತ್ತಿದೆ."</string>
<string name="app_suspended_more_details" msgid="211260942831587014">"ಇನ್ನಷ್ಟು ತಿಳಿಯಿರಿ"</string>
<string name="app_suspended_unsuspend_message" msgid="1665438589450555459">"ಆ್ಯಪ್ ವಿರಾಮ ನಿಲ್ಲಿಸಿ"</string>
<string name="work_mode_off_title" msgid="6367463960165135829">"ಕೆಲಸದ ಆ್ಯಪ್ ವಿರಾಮ ರದ್ದುಮಾಡಬೇಕೇ"</string>
@@ -2038,7 +2038,7 @@
<string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"ಅಪ್‌ಡೇಟ್‌ಗಾಗಿ ಪರಿಶೀಲಿಸಿ"</string>
<string name="deprecated_abi_message" msgid="6820548011196218091">"ಈ ಆ್ಯಪ್ Android ನ ಇತ್ತೀಚಿನ ಆವೃತ್ತಿಯ ಜೊತೆಗೆ ಹೊಂದಿಕೆಯಾಗುವುದಿಲ್ಲ. ಅಪ್‌ಡೇಟ್‌ಗಾಗಿ ಪರಿಶೀಲಿಸಿ ಅಥವಾ ಆ್ಯಪ್ ಡೆವಲಪರ್ ಅನ್ನು ಸಂಪರ್ಕಿಸಿ."</string>
<string name="new_sms_notification_title" msgid="6528758221319927107">"ನೀವು ಹೊಸ ಸಂದೇಶಗಳನ್ನು ಹೊಂದಿರುವಿರಿ"</string>
- <string name="new_sms_notification_content" msgid="3197949934153460639">"ವೀಕ್ಷಿಸಲು SMS ಅಪ್ಲಿಕೇಶನ್ ತೆರೆಯಿರಿ"</string>
+ <string name="new_sms_notification_content" msgid="3197949934153460639">"ವೀಕ್ಷಿಸಲು SMS ಆ್ಯಪ್ ತೆರೆಯಿರಿ"</string>
<string name="profile_encrypted_title" msgid="9001208667521266472">"ಕೆಲವು ಕಾರ್ಯನಿರ್ವಹಣೆ ಸೀಮಿತವಾಗಿರಬಹುದು"</string>
<string name="profile_encrypted_detail" msgid="5279730442756849055">"ಕೆಲಸದ ಪ್ರೊಫೈಲ್ ಲಾಕ್ ಮಾಡಲಾಗಿದೆ"</string>
<string name="profile_encrypted_message" msgid="1128512616293157802">"ಕೆಲಸದ ಪ್ರೊಫೈಲ್ ಅನ್‌ಲಾಕ್ ಮಾಡಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
@@ -2116,14 +2116,14 @@
<string name="mmcc_illegal_me_msim_template" msgid="4802735138861422802">"<xliff:g id="SIMNUMBER">%d</xliff:g> ಸಿಮ್ ಅನ್ನು ಅನುಮತಿಸಲಾಗಿಲ್ಲ"</string>
<string name="popup_window_default_title" msgid="6907717596694826919">"ಪಾಪ್‌ಅಪ್ ವಿಂಡೋ"</string>
<string name="slice_more_content" msgid="3377367737876888459">"+ <xliff:g id="NUMBER">%1$d</xliff:g>"</string>
- <string name="shortcut_restored_on_lower_version" msgid="9206301954024286063">"ಅಪ್ಲಿಕೇಶನ್ ಆವೃತ್ತಿಯನ್ನು ಡೌನ್‌ಗ್ರೇಡ್‌ ಮಾಡಲಾಗಿದೆ ಅಥವಾ ಈ ಶಾರ್ಟ್‌ಕಟ್‌ಗೆ ಹೊಂದಾಣಿಕೆಯಾಗುವುದಿಲ್ಲ"</string>
- <string name="shortcut_restore_not_supported" msgid="4763198938588468400">"ಅಪ್ಲಿಕೇಶನ್‌ ಬ್ಯಾಕಪ್ ಮತ್ತು ಪುನಃಸ್ಥಾಪನೆಯನ್ನು ಬೆಂಬಲಿಸದಿರುವುದರಿಂದ ಶಾರ್ಟ್‌ಕಟ್‌ ಅನ್ನು ಪುನಃಸ್ಥಾಪನೆ ಮಾಡಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ"</string>
- <string name="shortcut_restore_signature_mismatch" msgid="579345304221605479">"ಅಪ್ಲಿಕೇಶನ್‌ ಸಹಿ ಹೊಂದಿಕೆಯಾಗದ ಕಾರಣದಿಂದ ಶಾರ್ಟ್‌ಕಟ್‌ ಅನ್ನು ಪುನಃಸ್ಥಾಪಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ"</string>
+ <string name="shortcut_restored_on_lower_version" msgid="9206301954024286063">"ಆ್ಯಪ್‌ ಆವೃತ್ತಿಯನ್ನು ಡೌನ್‌ಗ್ರೇಡ್‌ ಮಾಡಲಾಗಿದೆ ಅಥವಾ ಈ ಶಾರ್ಟ್‌ಕಟ್‌ಗೆ ಹೊಂದಾಣಿಕೆಯಾಗುವುದಿಲ್ಲ"</string>
+ <string name="shortcut_restore_not_supported" msgid="4763198938588468400">"ಆ್ಯಪ್‌ ಬ್ಯಾಕಪ್ ಮತ್ತು ಪುನಃಸ್ಥಾಪನೆಯನ್ನು ಬೆಂಬಲಿಸದಿರುವುದರಿಂದ ಶಾರ್ಟ್‌ಕಟ್‌ ಅನ್ನು ಪುನಃಸ್ಥಾಪನೆ ಮಾಡಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ"</string>
+ <string name="shortcut_restore_signature_mismatch" msgid="579345304221605479">"ಆ್ಯಪ್‌ ಸಹಿ ಹೊಂದಿಕೆಯಾಗದ ಕಾರಣದಿಂದ ಶಾರ್ಟ್‌ಕಟ್‌ ಅನ್ನು ಪುನಃಸ್ಥಾಪಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ"</string>
<string name="shortcut_restore_unknown_issue" msgid="2478146134395982154">"ಶಾರ್ಟ್‌ಕಟ್‌ ಅನ್ನು ಪುನಃ ಸ್ಥಾಪನೆ ಮಾಡಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ"</string>
<string name="shortcut_disabled_reason_unknown" msgid="753074793553599166">"ಶಾರ್ಟ್‌ಕಟ್ ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ"</string>
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"ಅನ್‌ಇನ್‌ಸ್ಟಾಲ್ ಮಾಡಿ"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"ಹೇಗಿದ್ದರೂ ತೆರೆಯಿರಿ"</string>
- <string name="harmful_app_warning_title" msgid="8794823880881113856">"ಅಪಾಯಕಾರಿ ಅಪ್ಲಿಕೇಶನ್ ಕಂಡುಬಂದಿದೆ"</string>
+ <string name="harmful_app_warning_title" msgid="8794823880881113856">"ಅಪಾಯಕಾರಿ ಆ್ಯಪ್‌ ಕಂಡುಬಂದಿದೆ"</string>
<string name="slices_permission_request" msgid="3677129866636153406">"<xliff:g id="APP_2">%2$s</xliff:g> ಸ್ಲೈಸ್‌ಗಳನ್ನು <xliff:g id="APP_0">%1$s</xliff:g> ತೋರಿಸಲು ಬಯಸಿದೆ"</string>
<string name="screenshot_edit" msgid="7408934887203689207">"ಎಡಿಟ್"</string>
<string name="volume_dialog_ringer_guidance_vibrate" msgid="2055927873175228519">"ಕರೆಗಳು ಮತ್ತು ಅಧಿಸೂಚನೆಗಳು ವೈಬ್ರೇಟ್‌ ಆಗುತ್ತವೆ"</string>
diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml
index 514b9cfb380f..14e1064a9aec 100644
--- a/core/res/res/values-ky/strings.xml
+++ b/core/res/res/values-ky/strings.xml
@@ -656,8 +656,8 @@
<string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"Улантуу үчүн экрандын кулпусун киргизиңиз"</string>
<string name="fingerprint_acquired_partial" msgid="4323789264604479684">"Сенсорду катуу басыңыз"</string>
<string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"Манжа изи таанылган жок. Кайра аракет кылыңыз."</string>
- <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Манжа изинин сенсорун тазалап, кайра аракет кылыңыз"</string>
- <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Сенсорду тазалап, кайра аракет кылыңыз"</string>
+ <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"Манжа изинин сенсорун тазалап, кайталап көрүңүз"</string>
+ <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"Сенсорду тазалап, кайталап көрүңүз"</string>
<string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"Сенсорду катуу басыңыз"</string>
<string name="fingerprint_acquired_too_slow" msgid="6683510291554497580">"Манжа өтө жай жылды. Кайталап көрүңүз."</string>
<string name="fingerprint_acquired_already_enrolled" msgid="2285166003936206785">"Башка манжа изин байкап көрүңүз"</string>
@@ -2390,7 +2390,7 @@
<string name="mic_access_on_toast" msgid="2666925317663845156">"Микрофон жеткиликтүү"</string>
<string name="mic_access_off_toast" msgid="8111040892954242437">"Микрофон бөгөттөлгөн"</string>
<string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"Башка экранга чыгаруу мүмкүн эмес"</string>
- <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Башка кабелди колдонуп, кайра аракет кылыңыз"</string>
+ <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Башка кабелди колдонуп, кайталап көрүңүз"</string>
<string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"Түзмөгүңүз өтө ысып кетти жана ал муздамайынча башка экранга чыгара албайт"</string>
<string name="concurrent_display_notification_name" msgid="1526911253558311131">"Кош экран"</string>
<string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"Dual Screen күйүк"</string>
diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml
index 83a1d56716cd..57f1472b6f34 100644
--- a/core/res/res/values-ms/strings.xml
+++ b/core/res/res/values-ms/strings.xml
@@ -1735,7 +1735,7 @@
<string name="accessibility_shortcut_menu_item_status_on" msgid="6608392117189732543">"HIDUP"</string>
<string name="accessibility_shortcut_menu_item_status_off" msgid="5531598275559472393">"MATI"</string>
<string name="accessibility_enable_service_title" msgid="3931558336268541484">"Benarkan <xliff:g id="SERVICE">%1$s</xliff:g> mempunyai kawalan penuh atas peranti anda?"</string>
- <string name="accessibility_service_warning_description" msgid="291674995220940133">"Kawalan penuh sesuai untuk apl yang membantu anda berkaitan dengan keperluan kebolehaksesan tetapi bukan untuk kebanyakan apl."</string>
+ <string name="accessibility_service_warning_description" msgid="291674995220940133">"Kawalan penuh sesuai untuk apl yang berkaitan dengan keperluan kebolehaksesan anda tetapi bukan untuk kebanyakan apl."</string>
<string name="accessibility_service_screen_control_title" msgid="190017412626919776">"Melihat dan mengawal skrin"</string>
<string name="accessibility_service_screen_control_description" msgid="6946315917771791525">"Ciri ini boleh membaca semua kandungan pada skrin dan memaparkan kandungan di atas apl lain."</string>
<string name="accessibility_service_action_perform_title" msgid="779670378951658160">"Melihat dan melaksanakan tindakan"</string>
diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml
index e4e75c1e3d2f..f568d7112cc0 100644
--- a/core/res/res/values-my/strings.xml
+++ b/core/res/res/values-my/strings.xml
@@ -797,7 +797,7 @@
<string name="permlab_accessNotifications" msgid="7130360248191984741">"သတိပေးချက်များအား အသုံးပြုခွင့်"</string>
<string name="permdesc_accessNotifications" msgid="761730149268789668">"အပလီကေးရှင်းကို အကြောင်းကြားချက်များအား ထုတ်လုပ်ရန်၊ လေ့လာရန်၊ ဖျက်ရန် ခွင့်ပြုခြင်း။ တခြား အပလီကေးရှင်းများမှ သတိပေးချက်များလည်း ပါဝင်ပါသည်"</string>
<string name="permlab_bindNotificationListenerService" msgid="5848096702733262458">"သတိပေးချက် နားထောင်ခြင်း ဆားဗစ် နှင့် ပူးပေါင်းခြင်း"</string>
- <string name="permdesc_bindNotificationListenerService" msgid="4970553694467137126">"ဖုန်းကိုင်ထားသူနှင့် အကြောင်းကြားချက် နားစွင့်သော ဆားဗစ်မှ ထိပ်ပိုင်းအင်တာဖေ့စ် ကို ပူးပေါင်းခွင့်ပေးခြင်း။ ပုံမှန် အက်ပ်များအတွက် ဘယ်တော့မှ မလိုအပ်ပါ"</string>
+ <string name="permdesc_bindNotificationListenerService" msgid="4970553694467137126">"ဖုန်းကိုင်ထားသူနှင့် အကြောင်းကြားချက် နားစွင့်သော ဆားဗစ်မှ ထိပ်ပိုင်းအင်တာဖေ့စ် ကို ပူးပေါင်းခွင့်ပေးသည်။ ပုံမှန် အက်ပ်များအတွက် ဘယ်တော့မှ မလိုအပ်ပါ။"</string>
<string name="permlab_bindConditionProviderService" msgid="5245421224814878483">"အခြေအနေ စီမံပေးရေး ဝန်ဆောင်မှု တစ်ခုဆီသို့ ချိတ်တွဲခြင်း"</string>
<string name="permdesc_bindConditionProviderService" msgid="6106018791256120258">"စွဲကိုင်ထားသူအား အခြေအနေကို စီမံပေးသူ၏ ထိပ်သီး အဆင့် အင်တာဖေ့စ်သို့ ချိတ်တွဲခွင့်ကို ပေးသည်။ သာမန် အက်ပ်များ အတွက် ဘယ်တော့မှ မလိုအပ်နိုင်ပါ။"</string>
<string name="permlab_bindDreamService" msgid="4776175992848982706">"အိပ်မက် ဝန်ဆောင်မှုသို့ ပေါင်းစည်းမည်"</string>
diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml
index 133429b2fcf7..914c04c65495 100644
--- a/core/res/res/values-ne/strings.xml
+++ b/core/res/res/values-ne/strings.xml
@@ -2410,7 +2410,7 @@
<string name="keyboard_layout_notification_multiple_selected_message" msgid="6576533454124419202">"किबोर्डहरू हेर्न ट्याप गर्नुहोस्"</string>
<string name="profile_label_private" msgid="6463418670715290696">"निजी"</string>
<string name="profile_label_clone" msgid="769106052210954285">"क्लोन"</string>
- <string name="profile_label_work" msgid="3495359133038584618">"कार्य प्रोफाइल"</string>
+ <string name="profile_label_work" msgid="3495359133038584618">"कार्य"</string>
<string name="profile_label_work_2" msgid="4691533661598632135">"कार्य प्रोफाइल २"</string>
<string name="profile_label_work_3" msgid="4834572253956798917">"कार्य प्रोफाइल ३"</string>
<string name="profile_label_test" msgid="9168641926186071947">"परीक्षण"</string>
diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml
index ff07066e186b..99ef60484276 100644
--- a/core/res/res/values-pt-rBR/strings.xml
+++ b/core/res/res/values-pt-rBR/strings.xml
@@ -2150,11 +2150,11 @@
<string name="notification_feedback_indicator_promoted" msgid="9030204303764698640">"Esta notificação foi classificada com maior prioridade. Toque para enviar seu feedback."</string>
<string name="notification_feedback_indicator_demoted" msgid="8880309924296450875">"Esta notificação foi classificada com menor prioridade. Toque para enviar seu feedback."</string>
<string name="nas_upgrade_notification_title" msgid="8436359459300146555">"Notificações avançadas"</string>
- <string name="nas_upgrade_notification_content" msgid="5157550369837103337">"As ações e respostas sugeridas agora são fornecidas pelas notificações avançadas. As Notificações adaptáveis do Android não estão mais disponíveis."</string>
+ <string name="nas_upgrade_notification_content" msgid="5157550369837103337">"As ações e respostas sugeridas agora são fornecidas pelas notificações avançadas. As notificações adaptativas do Android não estão mais disponíveis."</string>
<string name="nas_upgrade_notification_enable_action" msgid="3046406808378726874">"OK"</string>
<string name="nas_upgrade_notification_disable_action" msgid="3794833210043497982">"Desativar"</string>
<string name="nas_upgrade_notification_learn_more_action" msgid="7011130656195423947">"Saiba mais"</string>
- <string name="nas_upgrade_notification_learn_more_content" msgid="3735480566983530650">"No Android 12, as notificações avançadas substituíram as notificações adaptáveis. Esse recurso mostra ações e respostas sugeridas, além de organizar suas notificações.\n\nAs notificações avançadas podem acessar o conteúdo das notificações, incluindo informações pessoais como nomes de contatos e mensagens. Elas também podem dispensar ou responder às notificações, como atender ligações telefônicas e controlar o Não perturbe."</string>
+ <string name="nas_upgrade_notification_learn_more_content" msgid="3735480566983530650">"No Android 12, as notificações avançadas substituíram as notificações adaptativas. Esse recurso mostra ações e respostas sugeridas, além de organizar suas notificações.\n\nAs notificações avançadas podem acessar o conteúdo das notificações, incluindo informações pessoais como nomes de contatos e mensagens. Elas também podem dispensar ou responder às notificações, como atender ligações telefônicas e controlar o Não perturbe."</string>
<string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Notificação de informação do modo rotina"</string>
<string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Economia de bateria ativada"</string>
<string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Reduzindo o uso da bateria para prolongar a duração dela"</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index ff07066e186b..99ef60484276 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -2150,11 +2150,11 @@
<string name="notification_feedback_indicator_promoted" msgid="9030204303764698640">"Esta notificação foi classificada com maior prioridade. Toque para enviar seu feedback."</string>
<string name="notification_feedback_indicator_demoted" msgid="8880309924296450875">"Esta notificação foi classificada com menor prioridade. Toque para enviar seu feedback."</string>
<string name="nas_upgrade_notification_title" msgid="8436359459300146555">"Notificações avançadas"</string>
- <string name="nas_upgrade_notification_content" msgid="5157550369837103337">"As ações e respostas sugeridas agora são fornecidas pelas notificações avançadas. As Notificações adaptáveis do Android não estão mais disponíveis."</string>
+ <string name="nas_upgrade_notification_content" msgid="5157550369837103337">"As ações e respostas sugeridas agora são fornecidas pelas notificações avançadas. As notificações adaptativas do Android não estão mais disponíveis."</string>
<string name="nas_upgrade_notification_enable_action" msgid="3046406808378726874">"OK"</string>
<string name="nas_upgrade_notification_disable_action" msgid="3794833210043497982">"Desativar"</string>
<string name="nas_upgrade_notification_learn_more_action" msgid="7011130656195423947">"Saiba mais"</string>
- <string name="nas_upgrade_notification_learn_more_content" msgid="3735480566983530650">"No Android 12, as notificações avançadas substituíram as notificações adaptáveis. Esse recurso mostra ações e respostas sugeridas, além de organizar suas notificações.\n\nAs notificações avançadas podem acessar o conteúdo das notificações, incluindo informações pessoais como nomes de contatos e mensagens. Elas também podem dispensar ou responder às notificações, como atender ligações telefônicas e controlar o Não perturbe."</string>
+ <string name="nas_upgrade_notification_learn_more_content" msgid="3735480566983530650">"No Android 12, as notificações avançadas substituíram as notificações adaptativas. Esse recurso mostra ações e respostas sugeridas, além de organizar suas notificações.\n\nAs notificações avançadas podem acessar o conteúdo das notificações, incluindo informações pessoais como nomes de contatos e mensagens. Elas também podem dispensar ou responder às notificações, como atender ligações telefônicas e controlar o Não perturbe."</string>
<string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Notificação de informação do modo rotina"</string>
<string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Economia de bateria ativada"</string>
<string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Reduzindo o uso da bateria para prolongar a duração dela"</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index 8d69834eaed9..c002866aa6ad 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -1338,7 +1338,7 @@
<string name="network_available_sign_in" msgid="1520342291829283114">"Ingia katika mtandao"</string>
<!-- no translation found for network_available_sign_in_detailed (7520423801613396556) -->
<skip />
- <string name="wifi_no_internet" msgid="1386911698276448061">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> haina uwezo wa kufikia intaneti"</string>
+ <string name="wifi_no_internet" msgid="1386911698276448061">"<xliff:g id="NETWORK_SSID">%1$s</xliff:g> haina intaneti"</string>
<string name="wifi_no_internet_detailed" msgid="634938444133558942">"Gusa ili upate chaguo"</string>
<string name="mobile_no_internet" msgid="4014455157529909781">"Mtandao wa simu hauna uwezo wa kufikia intaneti"</string>
<string name="other_networks_no_internet" msgid="6698711684200067033">"Mtandao hauna uwezo wa kufikia intaneti"</string>
@@ -2408,9 +2408,9 @@
<string name="keyboard_layout_notification_more_than_three_selected_message" msgid="1581834181578206937">"Muundo wa kibodi umewekwa kuwa <xliff:g id="LAYOUT_1">%1$s</xliff:g>, <xliff:g id="LAYOUT_2">%2$s</xliff:g>, <xliff:g id="LAYOUT_3">%3$s</xliff:g>… Gusa ili ubadilishe."</string>
<string name="keyboard_layout_notification_multiple_selected_title" msgid="5242444914367024499">"Mipangilio ya kibodi halisi imewekwa"</string>
<string name="keyboard_layout_notification_multiple_selected_message" msgid="6576533454124419202">"Gusa ili uangalie kibodi"</string>
- <string name="profile_label_private" msgid="6463418670715290696">"Wa faragha"</string>
+ <string name="profile_label_private" msgid="6463418670715290696">"Faragha"</string>
<string name="profile_label_clone" msgid="769106052210954285">"Nakala"</string>
- <string name="profile_label_work" msgid="3495359133038584618">"Wa kazini"</string>
+ <string name="profile_label_work" msgid="3495359133038584618">"Kazini"</string>
<string name="profile_label_work_2" msgid="4691533661598632135">"Wa 2 wa Kazini"</string>
<string name="profile_label_work_3" msgid="4834572253956798917">"Wa 3 wa Kazini"</string>
<string name="profile_label_test" msgid="9168641926186071947">"Jaribio"</string>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index 51bb90f71d2b..5ee187fa8a41 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -1735,7 +1735,7 @@
<string name="accessibility_shortcut_menu_item_status_on" msgid="6608392117189732543">"เปิด"</string>
<string name="accessibility_shortcut_menu_item_status_off" msgid="5531598275559472393">"ปิด"</string>
<string name="accessibility_enable_service_title" msgid="3931558336268541484">"อนุญาตให้ <xliff:g id="SERVICE">%1$s</xliff:g> ควบคุมอุปกรณ์อย่างเต็มที่ไหม"</string>
- <string name="accessibility_service_warning_description" msgid="291674995220940133">"การควบคุมอย่างเต็มที่เหมาะสำหรับแอปเกี่ยวกับความช่วยเหลือพิเศษ แต่ไม่เหมาะสำหรับ​แอปส่วนใหญ่"</string>
+ <string name="accessibility_service_warning_description" msgid="291674995220940133">"การควบคุมอย่างเต็มรูปแบบเหมาะสำหรับแอปเกี่ยวกับความช่วยเหลือพิเศษ แต่ไม่เหมาะสำหรับ​แอปส่วนใหญ่"</string>
<string name="accessibility_service_screen_control_title" msgid="190017412626919776">"ดูและควบคุมหน้าจอ"</string>
<string name="accessibility_service_screen_control_description" msgid="6946315917771791525">"การควบคุมนี้สามารถอ่านเนื้อหาทั้งหมดบนหน้าจอและแสดงเนื้อหาทับแอปอื่นๆ"</string>
<string name="accessibility_service_action_perform_title" msgid="779670378951658160">"ดูและดำเนินการ"</string>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 07efad89010a..5c0dca2104af 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4457,6 +4457,11 @@
<!-- Bytes that the PinnerService will pin for WebView -->
<integer name="config_pinnerWebviewPinBytes">0</integer>
+ <!-- Maximum memory that PinnerService will pin for apps expressed
+ as a percentage of total device memory [0,100].
+ Example: 10, means 10% of total memory will be the maximum pinned memory -->
+ <integer name="config_pinnerMaxPinnedMemoryPercentage">10</integer>
+
<!-- Number of days preloaded file cache should be preserved on a device before it can be
deleted -->
<integer name="config_keepPreloadsMinDays">7</integer>
@@ -6178,6 +6183,10 @@
is enabled and activity is connected to the camera in fullscreen. -->
<bool name="config_isWindowManagerCameraCompatSplitScreenAspectRatioEnabled">false</bool>
+ <!-- Which aspect ratio to use when camera compat treatment is enabled and an activity eligible
+ for treatment is connected to the camera. -->
+ <item name="config_windowManagerCameraCompatAspectRatio" format="float" type="dimen">1.0</item>
+
<!-- Docking is a uiMode configuration change and will cause activities to relaunch if it's not
handled. If true, the configuration change will be sent but activities will not be
relaunched upon docking. Apps with desk resources will behave like normal, since they may
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index f397ef2b151c..6683dc044c9a 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -808,6 +808,12 @@
This is bigger than displayed because listeners can use it for other displays
e.g. wearables. -->
<dimen name="notification_person_icon_max_size">144dp</dimen>
+ <!-- The size of the progress bar icon -->
+ <dimen name="notification_progress_icon_size">20dp</dimen>
+ <!-- The size of the progress tracker width -->
+ <dimen name="notification_progress_tracker_width">40dp</dimen>
+ <!-- The size of the progress tracker height -->
+ <dimen name="notification_progress_tracker_height">20dp</dimen>
<!-- The maximum size of the small notification icon on low memory devices. -->
<dimen name="notification_small_icon_size_low_ram">@dimen/notification_small_icon_size</dimen>
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index b74b41c666c8..81215453b3ef 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -118,6 +118,9 @@
<public name="languageSettingsActivity"/>
<!-- @FlaggedApi("android.service.controls.flags.Flags.FLAG_HOME_PANEL_DREAM") -->
<public name="dreamCategory"/>
+ <!-- @FlaggedApi("android.permission.flags.replace_body_sensor_permission_enabled")
+ @hide @SystemApi -->
+ <public name="backgroundPermission"/>
</staging-public-group>
<staging-public-group type="id" first-id="0x01b60000">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 06b36b8f74af..807df1be7fb5 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3467,6 +3467,7 @@
<java-symbol type="integer" name="config_pinnerHomePinBytes" />
<java-symbol type="bool" name="config_pinnerAssistantApp" />
<java-symbol type="integer" name="config_pinnerWebviewPinBytes" />
+ <java-symbol type="integer" name="config_pinnerMaxPinnedMemoryPercentage" />
<java-symbol type="string" name="config_doubleTouchGestureEnableFile" />
@@ -3854,6 +3855,9 @@
<java-symbol type="dimen" name="notification_custom_view_max_image_height"/>
<java-symbol type="dimen" name="notification_custom_view_max_image_width"/>
<java-symbol type="dimen" name="notification_person_icon_max_size" />
+ <java-symbol type="dimen" name="notification_progress_icon_size" />
+ <java-symbol type="dimen" name="notification_progress_tracker_width" />
+ <java-symbol type="dimen" name="notification_progress_tracker_height" />
<java-symbol type="dimen" name="notification_small_icon_size_low_ram"/>
<java-symbol type="dimen" name="notification_big_picture_max_height_low_ram"/>
@@ -4774,6 +4778,7 @@
<java-symbol type="bool" name="config_isCompatFakeFocusEnabled" />
<java-symbol type="bool" name="config_isWindowManagerCameraCompatTreatmentEnabled" />
<java-symbol type="bool" name="config_isWindowManagerCameraCompatSplitScreenAspectRatioEnabled" />
+ <java-symbol type="dimen" name="config_windowManagerCameraCompatAspectRatio" />
<java-symbol type="bool" name="config_skipActivityRelaunchWhenDocking" />
<java-symbol type="bool" name="config_hideDisplayCutoutWithDisplayArea" />
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index 0f73df92ca93..e9b137cd5477 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -97,7 +97,6 @@ import android.text.style.ForegroundColorSpan;
import android.text.style.StyleSpan;
import android.text.style.TextAppearanceSpan;
import android.util.Pair;
-import android.util.Slog;
import android.widget.RemoteViews;
import androidx.test.InstrumentationRegistry;
@@ -118,6 +117,7 @@ import org.junit.Test;
import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
+import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
@@ -439,6 +439,27 @@ public class NotificationTest {
}
@Test
+ @EnableFlags({Flags.FLAG_UI_RICH_ONGOING, Flags.FLAG_API_RICH_ONGOING})
+ public void testHasPromotableStyle_progress() {
+ Notification n = new Notification.Builder(mContext, "test")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setStyle(new Notification.ProgressStyle())
+ .build();
+ assertThat(n.hasPromotableStyle()).isTrue();
+ }
+
+ @Test
+ @EnableFlags({Flags.FLAG_UI_RICH_ONGOING})
+ public void testHasPromotableStyle_unknownStyle() {
+ Notification n = new Notification.Builder(mContext, "test")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setStyle(new NotAPlatformStyle())
+ .build();
+
+ assertThat(n.hasPromotableStyle()).isTrue();
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
public void testHasPromotableCharacteristics() {
Notification n = new Notification.Builder(mContext, "test")
@@ -2114,6 +2135,300 @@ public class NotificationTest {
assertThat(n.getWhen()).isEqualTo(9);
}
+ @Test
+ public void getNotificationStyleClass_forPlatformClassName_returnsPlatformClass() {
+ final List<Class<? extends Notification.Style>> platformStyleClasses = List.of(
+ Notification.BigTextStyle.class, Notification.BigPictureStyle.class,
+ Notification.MessagingStyle.class, Notification.CallStyle.class,
+ Notification.InboxStyle.class, Notification.MediaStyle.class,
+ Notification.DecoratedCustomViewStyle.class,
+ Notification.DecoratedMediaCustomViewStyle.class
+ );
+
+ for (Class<? extends Notification.Style> platformStyleClass : platformStyleClasses) {
+ assertThat(Notification.getNotificationStyleClass(platformStyleClass.getName()))
+ .isEqualTo(platformStyleClass);
+ }
+ }
+
+ @Test
+ public void getNotificationStyleClass_forNotPlatformClassName_returnsNull() {
+ assertThat(Notification.getNotificationStyleClass(NotAPlatformStyle.class.getName()))
+ .isNull();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void progressStyle_richOngoingEnabled_platformClass() {
+ assertThat(
+ Notification.getNotificationStyleClass(Notification.ProgressStyle.class.getName()))
+ .isEqualTo(Notification.ProgressStyle.class);
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void progressStyle_richOngoingDisabled_notPlatformClass() {
+ assertThat(
+ Notification.getNotificationStyleClass(Notification.ProgressStyle.class.getName()))
+ .isNull();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void progressStyle_onSegmentChange_visiblyDifferent() {
+ final Notification.Builder nProgress1 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.ProgressStyle()
+ .addProgressSegment(new Notification.ProgressStyle.Segment(100))
+ .addProgressSegment(new Notification.ProgressStyle.Segment(50)
+ .setColor(Color.RED)));
+
+ final Notification.Builder nProgress2 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.ProgressStyle()
+ .addProgressSegment(new Notification.ProgressStyle.Segment(100))
+ .addProgressSegment(new Notification.ProgressStyle.Segment(50)
+ .setColor(Color.BLUE)));
+
+ assertThat(Notification.areStyledNotificationsVisiblyDifferent(nProgress1, nProgress2))
+ .isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void indeterminateProgressStyle_onSegmentChange_visiblyNotDifferent() {
+ final Notification.Builder nProgress1 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.ProgressStyle().setProgressIndeterminate(true)
+ .addProgressSegment(new Notification.ProgressStyle.Segment(100))
+ .addProgressSegment(new Notification.ProgressStyle.Segment(50)
+ .setColor(Color.RED)));
+
+ final Notification.Builder nProgress2 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.ProgressStyle().setProgressIndeterminate(true)
+ .addProgressSegment(new Notification.ProgressStyle.Segment(100))
+ .addProgressSegment(new Notification.ProgressStyle.Segment(50)
+ .setColor(Color.BLUE)));
+
+ assertThat(Notification.areStyledNotificationsVisiblyDifferent(nProgress1, nProgress2))
+ .isFalse();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void progressStyle_onStartIconChange_visiblyDifferent() {
+ final Icon icon1 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+ mContext.getResources(), com.android.frameworks.coretests.R.drawable.test128x96));
+
+ final Icon icon2 = Icon.createWithBitmap(
+ Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888));
+
+ final Notification.Builder nProgress1 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.ProgressStyle().setProgressStartIcon(icon1));
+ final Notification.Builder nProgress2 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.ProgressStyle().setProgressStartIcon(icon2));
+
+ assertThat(Notification.areStyledNotificationsVisiblyDifferent(nProgress1, nProgress2))
+ .isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void progressStyle_onEndIconChange_visiblyDifferent() {
+ final Icon icon1 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+ mContext.getResources(), com.android.frameworks.coretests.R.drawable.test128x96));
+
+ final Icon icon2 = Icon.createWithBitmap(
+ Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888));
+
+ final Notification.Builder nProgress1 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.ProgressStyle().setProgressEndIcon(icon1));
+ final Notification.Builder nProgress2 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.ProgressStyle().setProgressEndIcon(icon2));
+
+ assertThat(Notification.areStyledNotificationsVisiblyDifferent(nProgress1, nProgress2))
+ .isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void progressStyle_onProgressChange_visiblyDifferent() {
+ final Notification.Builder nProgress1 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.ProgressStyle().setProgress(20));
+ final Notification.Builder nProgress2 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.ProgressStyle().setProgress(21));
+
+ assertThat(Notification.areStyledNotificationsVisiblyDifferent(nProgress1, nProgress2))
+ .isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void indeterminateProgressStyle_onProgressChange_visiblyNotDifferent() {
+ final Notification.Builder nProgress1 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.ProgressStyle()
+ .setProgressIndeterminate(true).setProgress(20));
+ final Notification.Builder nProgress2 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.ProgressStyle()
+ .setProgressIndeterminate(true).setProgress(21));
+
+ assertThat(Notification.areStyledNotificationsVisiblyDifferent(nProgress1, nProgress2))
+ .isFalse();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void progressStyle_onIsStyledByProgressChange_visiblyDifferent() {
+ final Notification.Builder nProgress1 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.ProgressStyle().setStyledByProgress(true));
+ final Notification.Builder nProgress2 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.ProgressStyle().setStyledByProgress(false));
+
+ assertThat(Notification.areStyledNotificationsVisiblyDifferent(nProgress1, nProgress2))
+ .isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void indeterminateProgressStyle_onIsStyledByProgressChange_visiblyNotDifferent() {
+ final Notification.Builder nProgress1 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.ProgressStyle()
+ .setProgressIndeterminate(true).setStyledByProgress(true));
+ final Notification.Builder nProgress2 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.ProgressStyle()
+ .setProgressIndeterminate(true).setStyledByProgress(false));
+
+ assertThat(Notification.areStyledNotificationsVisiblyDifferent(nProgress1, nProgress2))
+ .isFalse();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void progressStyle_onProgressStepChange_visiblyDifferent() {
+ final Notification.Builder nProgress1 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.ProgressStyle()
+ .addProgressStep(new Notification.ProgressStyle.Step(10)));
+ final Notification.Builder nProgress2 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.ProgressStyle()
+ .addProgressStep(new Notification.ProgressStyle.Step(12)));
+
+ assertThat(Notification.areStyledNotificationsVisiblyDifferent(nProgress1, nProgress2))
+ .isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void indeterminateProgressStyle_onProgressStepChange_visiblyNotDifferent() {
+ final Notification.Builder nProgress1 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.ProgressStyle().setProgressIndeterminate(true)
+ .addProgressStep(new Notification.ProgressStyle.Step(10)));
+ final Notification.Builder nProgress2 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.ProgressStyle().setProgressIndeterminate(true)
+ .addProgressStep(new Notification.ProgressStyle.Step(12)));
+
+ assertThat(Notification.areStyledNotificationsVisiblyDifferent(nProgress1, nProgress2))
+ .isFalse();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void progressStyle_onTrackerIconChange_visiblyDifferent() {
+ final Icon icon1 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+ mContext.getResources(), com.android.frameworks.coretests.R.drawable.test128x96));
+
+ final Icon icon2 = Icon.createWithBitmap(
+ Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888));
+
+ final Notification.Builder nProgress1 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.ProgressStyle().setProgressTrackerIcon(icon1));
+ final Notification.Builder nProgress2 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.ProgressStyle().setProgressTrackerIcon(icon2));
+
+ assertThat(Notification.areStyledNotificationsVisiblyDifferent(nProgress1, nProgress2))
+ .isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void indeterminateProgressStyle_onTrackerIconChange_visiblyNotDifferent() {
+ final Icon icon1 = Icon.createWithBitmap(BitmapFactory.decodeResource(
+ mContext.getResources(), com.android.frameworks.coretests.R.drawable.test128x96));
+
+ final Icon icon2 = Icon.createWithBitmap(
+ Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888));
+
+ final Notification.Builder nProgress1 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.ProgressStyle().setProgressIndeterminate(true)
+ .setProgressTrackerIcon(icon1));
+ final Notification.Builder nProgress2 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.ProgressStyle()
+ .setProgressIndeterminate(true).setProgressTrackerIcon(icon2));
+
+ assertThat(Notification.areStyledNotificationsVisiblyDifferent(nProgress1, nProgress2))
+ .isFalse();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void progressStyle_onIndeterminateChange_visiblyDifferent() {
+ final Notification.Builder nProgress1 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.ProgressStyle().setProgressIndeterminate(true));
+ final Notification.Builder nProgress2 = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.ProgressStyle().setProgressIndeterminate(false));
+
+ assertThat(Notification.areStyledNotificationsVisiblyDifferent(nProgress1, nProgress2))
+ .isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void progressStyle_getProgressMax_default100() {
+ final Notification.ProgressStyle progressStyle = new Notification.ProgressStyle();
+ assertThat(progressStyle.getProgressMax()).isEqualTo(100);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void progressStyle_getProgressMax_nooSegments_returnsDefault() {
+ final Notification.ProgressStyle progressStyle = new Notification.ProgressStyle();
+ progressStyle.setProgressSegments(Collections.emptyList());
+ assertThat(progressStyle.getProgressMax()).isEqualTo(100);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void progressStyle_getProgressMax_returnsSumOfSegmentLength() {
+ final Notification.ProgressStyle progressStyle = new Notification.ProgressStyle();
+ progressStyle
+ .addProgressSegment(new Notification.ProgressStyle.Segment(10))
+ .addProgressSegment(new Notification.ProgressStyle.Segment(20));
+
+ assertThat(progressStyle.getProgressMax()).isEqualTo(30);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void progressStyle_getProgressMax_onSegmentOverflow_returnsDefault() {
+ final Notification.ProgressStyle progressStyle = new Notification.ProgressStyle();
+ progressStyle
+ .addProgressSegment(new Notification.ProgressStyle.Segment(Integer.MAX_VALUE))
+ .addProgressSegment(new Notification.ProgressStyle.Segment(10));
+
+ assertThat(progressStyle.getProgressMax()).isEqualTo(100);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void progressStyle_indeterminate_defaultValueFalse() {
+ final Notification.ProgressStyle progressStyle1 = new Notification.ProgressStyle();
+
+ assertThat(progressStyle1.isProgressIndeterminate()).isFalse();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_API_RICH_ONGOING)
+ public void progressStyle_styledByProgress_defaultValueTrue() {
+ final Notification.ProgressStyle progressStyle1 = new Notification.ProgressStyle();
+
+ assertThat(progressStyle1.isStyledByProgress()).isTrue();
+ }
private void assertValid(Notification.Colors c) {
// Assert that all colors are populated
assertThat(c.getBackgroundColor()).isNotEqualTo(Notification.COLOR_INVALID);
@@ -2214,4 +2529,11 @@ public class NotificationTest {
new Intent(action).setPackage(mContext.getPackageName()),
PendingIntent.FLAG_MUTABLE);
}
+
+ private static class NotAPlatformStyle extends Notification.Style {
+ @Override
+ public boolean areNotificationsVisiblyDifferent(Notification.Style other) {
+ return false;
+ }
+ }
}
diff --git a/core/tests/coretests/src/android/view/WindowLayoutTests.java b/core/tests/coretests/src/android/view/WindowLayoutTests.java
index 5cac98daee80..d4693e6e7130 100644
--- a/core/tests/coretests/src/android/view/WindowLayoutTests.java
+++ b/core/tests/coretests/src/android/view/WindowLayoutTests.java
@@ -413,4 +413,19 @@ public class WindowLayoutTests {
assertInsetByTopBottom(0, 0, mFrames.parentFrame);
assertInsetByTopBottom(0, 0, mFrames.frame);
}
+
+ @Test
+ public void windowBoundsOutsideDisplayCutoutSafe() {
+ addDisplayCutout();
+ mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
+ mWindowBounds.set(0, -1000, DISPLAY_WIDTH, 0);
+ computeFrames();
+
+ assertRect(WATERFALL_INSETS.left, 0, DISPLAY_WIDTH - WATERFALL_INSETS.right, 0,
+ mFrames.displayFrame);
+ assertRect(WATERFALL_INSETS.left, 0, DISPLAY_WIDTH - WATERFALL_INSETS.right, 0,
+ mFrames.parentFrame);
+ assertRect(WATERFALL_INSETS.left, 0, DISPLAY_WIDTH - WATERFALL_INSETS.right, 0,
+ mFrames.frame);
+ }
}
diff --git a/graphics/java/android/graphics/PathIterator.java b/graphics/java/android/graphics/PathIterator.java
index 48b29f4e81f4..d7caabf9f91b 100644
--- a/graphics/java/android/graphics/PathIterator.java
+++ b/graphics/java/android/graphics/PathIterator.java
@@ -44,6 +44,8 @@ public class PathIterator implements Iterator<PathIterator.Segment> {
private final Path mPath;
private final int mPathGenerationId;
private static final int POINT_ARRAY_SIZE = 8;
+ private static final boolean IS_DALVIK = "dalvik".equalsIgnoreCase(
+ System.getProperty("java.vm.name"));
private static final NativeAllocationRegistry sRegistry =
NativeAllocationRegistry.createMalloced(
@@ -80,9 +82,14 @@ public class PathIterator implements Iterator<PathIterator.Segment> {
mPath = path;
mNativeIterator = nCreate(mPath.mNativePath);
mPathGenerationId = mPath.getGenerationId();
- final VMRuntime runtime = VMRuntime.getRuntime();
- mPointsArray = (float[]) runtime.newNonMovableArray(float.class, POINT_ARRAY_SIZE);
- mPointsAddress = runtime.addressOf(mPointsArray);
+ if (IS_DALVIK) {
+ final VMRuntime runtime = VMRuntime.getRuntime();
+ mPointsArray = (float[]) runtime.newNonMovableArray(float.class, POINT_ARRAY_SIZE);
+ mPointsAddress = runtime.addressOf(mPointsArray);
+ } else {
+ mPointsArray = new float[POINT_ARRAY_SIZE];
+ mPointsAddress = 0;
+ }
sRegistry.registerNativeAllocation(this, mNativeIterator);
}
@@ -177,7 +184,8 @@ public class PathIterator implements Iterator<PathIterator.Segment> {
throw new ConcurrentModificationException(
"Iterator cannot be used on modified Path");
}
- @Verb int verb = nNext(mNativeIterator, mPointsAddress);
+ @Verb int verb = IS_DALVIK
+ ? nNext(mNativeIterator, mPointsAddress) : nNextHost(mNativeIterator, mPointsArray);
if (verb == VERB_DONE) {
mDone = true;
}
@@ -287,6 +295,9 @@ public class PathIterator implements Iterator<PathIterator.Segment> {
private static native long nCreate(long nativePath);
private static native long nGetFinalizer();
+ /* nNextHost should be used for host runtimes, e.g. LayoutLib */
+ private static native int nNextHost(long nativeIterator, float[] points);
+
// ------------------ Critical JNI ------------------------
@CriticalNative
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java
index 37f0067de453..089613853555 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java
@@ -16,12 +16,11 @@
package androidx.window.common;
-import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER;
+import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
import static androidx.window.common.layout.CommonFoldingFeature.COMMON_STATE_UNKNOWN;
import static androidx.window.common.layout.CommonFoldingFeature.parseListFromString;
-import android.annotation.NonNull;
import android.content.Context;
import android.hardware.devicestate.DeviceState;
import android.hardware.devicestate.DeviceStateManager;
@@ -31,16 +30,23 @@ import android.text.TextUtils;
import android.util.Log;
import android.util.SparseIntArray;
+import androidx.annotation.BinderThread;
+import androidx.annotation.GuardedBy;
+import androidx.annotation.MainThread;
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
import androidx.window.common.layout.CommonFoldingFeature;
import androidx.window.common.layout.DisplayFoldFeatureCommon;
import com.android.internal.R;
+import com.android.window.flags.Flags;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
+import java.util.concurrent.Executor;
import java.util.function.Consumer;
/**
@@ -55,13 +61,6 @@ public final class DeviceStateManagerFoldingFeatureProducer
private static final boolean DEBUG = false;
/**
- * Emulated device state
- * {@link DeviceStateManager.DeviceStateCallback#onDeviceStateChanged(DeviceState)} to
- * {@link CommonFoldingFeature.State} map.
- */
- private final SparseIntArray mDeviceStateToPostureMap = new SparseIntArray();
-
- /**
* Device state received via
* {@link DeviceStateManager.DeviceStateCallback#onDeviceStateChanged(DeviceState)}.
* The identifier returned through {@link DeviceState#getIdentifier()} may not correspond 1:1
@@ -71,23 +70,40 @@ public final class DeviceStateManagerFoldingFeatureProducer
* "rear display". Concurrent mode for example is activated via public API and can be active in
* both the "open" and "half folded" device states.
*/
- private DeviceState mCurrentDeviceState = new DeviceState(
- new DeviceState.Configuration.Builder(INVALID_DEVICE_STATE_IDENTIFIER,
- "INVALID").build());
+ // TODO: b/337820752 - Add @GuardedBy("mCurrentDeviceStateLock") after flag cleanup.
+ private DeviceState mCurrentDeviceState = INVALID_DEVICE_STATE;
- private List<DeviceState> mSupportedStates;
+ /**
+ * Lock to synchronize access to {@link #mCurrentDeviceState}.
+ *
+ * <p>This lock is used to ensure thread-safety when accessing and modifying the
+ * {@link #mCurrentDeviceState} field. It is acquired by both the binder thread (if
+ * {@link Flags#wlinfoOncreate()} is enabled) and the main thread (if
+ * {@link Flags#wlinfoOncreate()} is disabled) to prevent race conditions and
+ * ensure data consistency.
+ */
+ private final Object mCurrentDeviceStateLock = new Object();
@NonNull
private final RawFoldingFeatureProducer mRawFoldSupplier;
- private final boolean mIsHalfOpenedSupported;
-
- private final DeviceStateCallback mDeviceStateCallback = new DeviceStateCallback() {
+ @NonNull
+ private final DeviceStateMapper mDeviceStateMapper;
+
+ @VisibleForTesting
+ final DeviceStateCallback mDeviceStateCallback = new DeviceStateCallback() {
+ // The GuardedBy analysis is intra-procedural, meaning it doesn’t consider the getData()
+ // implementation. See https://errorprone.info/bugpattern/GuardedBy for limitations.
+ @SuppressWarnings("GuardedBy")
+ @BinderThread // When Flags.wlinfoOncreate() is enabled.
+ @MainThread // When Flags.wlinfoOncreate() is disabled.
@Override
public void onDeviceStateChanged(@NonNull DeviceState state) {
- mCurrentDeviceState = state;
- mRawFoldSupplier.getData(DeviceStateManagerFoldingFeatureProducer
- .this::notifyFoldingFeatureChange);
+ synchronized (mCurrentDeviceStateLock) {
+ mCurrentDeviceState = state;
+ mRawFoldSupplier.getData(DeviceStateManagerFoldingFeatureProducer.this
+ ::notifyFoldingFeatureChangeLocked);
+ }
}
};
@@ -95,41 +111,14 @@ public final class DeviceStateManagerFoldingFeatureProducer
@NonNull RawFoldingFeatureProducer rawFoldSupplier,
@NonNull DeviceStateManager deviceStateManager) {
mRawFoldSupplier = rawFoldSupplier;
- String[] deviceStatePosturePairs = context.getResources()
- .getStringArray(R.array.config_device_state_postures);
- mSupportedStates = deviceStateManager.getSupportedDeviceStates();
- boolean isHalfOpenedSupported = false;
- for (String deviceStatePosturePair : deviceStatePosturePairs) {
- String[] deviceStatePostureMapping = deviceStatePosturePair.split(":");
- if (deviceStatePostureMapping.length != 2) {
- if (DEBUG) {
- Log.e(TAG, "Malformed device state posture pair: "
- + deviceStatePosturePair);
- }
- continue;
- }
+ mDeviceStateMapper =
+ new DeviceStateMapper(context, deviceStateManager.getSupportedDeviceStates());
- int deviceState;
- int posture;
- try {
- deviceState = Integer.parseInt(deviceStatePostureMapping[0]);
- posture = Integer.parseInt(deviceStatePostureMapping[1]);
- } catch (NumberFormatException e) {
- if (DEBUG) {
- Log.e(TAG, "Failed to parse device state or posture: "
- + deviceStatePosturePair,
- e);
- }
- continue;
- }
- isHalfOpenedSupported = isHalfOpenedSupported
- || posture == CommonFoldingFeature.COMMON_STATE_HALF_OPENED;
- mDeviceStateToPostureMap.put(deviceState, posture);
- }
- mIsHalfOpenedSupported = isHalfOpenedSupported;
- if (mDeviceStateToPostureMap.size() > 0) {
+ if (!mDeviceStateMapper.isDeviceStateToPostureMapEmpty()) {
+ final Executor executor =
+ Flags.wlinfoOncreate() ? Runnable::run : context.getMainExecutor();
Objects.requireNonNull(deviceStateManager)
- .registerCallback(context.getMainExecutor(), mDeviceStateCallback);
+ .registerCallback(executor, mDeviceStateCallback);
}
}
@@ -137,50 +126,51 @@ public final class DeviceStateManagerFoldingFeatureProducer
* Add a callback to mCallbacks if there is no device state. This callback will be run
* once a device state is set. Otherwise,run the callback immediately.
*/
- private void runCallbackWhenValidState(@NonNull Consumer<List<CommonFoldingFeature>> callback,
- String displayFeaturesString) {
- if (isCurrentStateValid()) {
- callback.accept(calculateFoldingFeature(displayFeaturesString));
+ private void runCallbackWhenValidState(@NonNull DeviceState state,
+ @NonNull Consumer<List<CommonFoldingFeature>> callback,
+ @NonNull String displayFeaturesString) {
+ if (mDeviceStateMapper.isDeviceStateValid(state)) {
+ callback.accept(calculateFoldingFeature(state, displayFeaturesString));
} else {
// This callback will be added to mCallbacks and removed once it runs once.
- AcceptOnceConsumer<List<CommonFoldingFeature>> singleRunCallback =
+ final AcceptOnceConsumer<List<CommonFoldingFeature>> singleRunCallback =
new AcceptOnceConsumer<>(this, callback);
addDataChangedCallback(singleRunCallback);
}
}
- /**
- * Checks to find {@link DeviceStateManagerFoldingFeatureProducer#mCurrentDeviceState} in the
- * {@link DeviceStateManagerFoldingFeatureProducer#mDeviceStateToPostureMap} which was
- * initialized in the constructor of {@link DeviceStateManagerFoldingFeatureProducer}.
- * Returns a boolean value of whether the device state is valid.
- */
- private boolean isCurrentStateValid() {
- // If the device state is not found in the map, indexOfKey returns a negative number.
- return mDeviceStateToPostureMap.indexOfKey(mCurrentDeviceState.getIdentifier()) >= 0;
- }
-
+ // The GuardedBy analysis is intra-procedural, meaning it doesn’t consider the implementation of
+ // addDataChangedCallback(). See https://errorprone.info/bugpattern/GuardedBy for limitations.
+ @SuppressWarnings("GuardedBy")
@Override
protected void onListenersChanged() {
super.onListenersChanged();
- if (hasListeners()) {
- mRawFoldSupplier.addDataChangedCallback(this::notifyFoldingFeatureChange);
- } else {
- mCurrentDeviceState = new DeviceState(
- new DeviceState.Configuration.Builder(INVALID_DEVICE_STATE_IDENTIFIER,
- "INVALID").build());
- mRawFoldSupplier.removeDataChangedCallback(this::notifyFoldingFeatureChange);
+ synchronized (mCurrentDeviceStateLock) {
+ if (hasListeners()) {
+ mRawFoldSupplier.addDataChangedCallback(this::notifyFoldingFeatureChangeLocked);
+ } else {
+ mCurrentDeviceState = INVALID_DEVICE_STATE;
+ mRawFoldSupplier.removeDataChangedCallback(this::notifyFoldingFeatureChangeLocked);
+ }
+ }
+ }
+
+ @NonNull
+ private DeviceState getCurrentDeviceState() {
+ synchronized (mCurrentDeviceStateLock) {
+ return mCurrentDeviceState;
}
}
@NonNull
@Override
public Optional<List<CommonFoldingFeature>> getCurrentData() {
- Optional<String> displayFeaturesString = mRawFoldSupplier.getCurrentData();
- if (!isCurrentStateValid()) {
+ final Optional<String> displayFeaturesString = mRawFoldSupplier.getCurrentData();
+ final DeviceState state = getCurrentDeviceState();
+ if (!mDeviceStateMapper.isDeviceStateValid(state) || displayFeaturesString.isEmpty()) {
return Optional.empty();
} else {
- return displayFeaturesString.map(this::calculateFoldingFeature);
+ return Optional.of(calculateFoldingFeature(state, displayFeaturesString.get()));
}
}
@@ -191,7 +181,7 @@ public final class DeviceStateManagerFoldingFeatureProducer
*/
@NonNull
public List<CommonFoldingFeature> getFoldsWithUnknownState() {
- Optional<String> optionalFoldingFeatureString = mRawFoldSupplier.getCurrentData();
+ final Optional<String> optionalFoldingFeatureString = mRawFoldSupplier.getCurrentData();
if (optionalFoldingFeatureString.isPresent()) {
return CommonFoldingFeature.parseListFromString(
@@ -201,7 +191,6 @@ public final class DeviceStateManagerFoldingFeatureProducer
return Collections.emptyList();
}
-
/**
* Returns the list of supported {@link DisplayFoldFeatureCommon} calculated from the
* {@link DeviceStateManagerFoldingFeatureProducer}.
@@ -218,16 +207,16 @@ public final class DeviceStateManagerFoldingFeatureProducer
return foldFeatures;
}
-
/**
* Returns {@code true} if the device supports half-opened mode, {@code false} otherwise.
*/
public boolean isHalfOpenedSupported() {
- return mIsHalfOpenedSupported;
+ return mDeviceStateMapper.mIsHalfOpenedSupported;
}
/**
* Adds the data to the storeFeaturesConsumer when the data is ready.
+ *
* @param storeFeaturesConsumer a consumer to collect the data when it is first available.
*/
@Override
@@ -236,38 +225,123 @@ public final class DeviceStateManagerFoldingFeatureProducer
if (TextUtils.isEmpty(displayFeaturesString)) {
storeFeaturesConsumer.accept(new ArrayList<>());
} else {
- runCallbackWhenValidState(storeFeaturesConsumer, displayFeaturesString);
+ final DeviceState state = getCurrentDeviceState();
+ runCallbackWhenValidState(state, storeFeaturesConsumer, displayFeaturesString);
}
});
}
- private void notifyFoldingFeatureChange(String displayFeaturesString) {
- if (!isCurrentStateValid()) {
+ @GuardedBy("mCurrentDeviceStateLock")
+ private void notifyFoldingFeatureChangeLocked(String displayFeaturesString) {
+ final DeviceState state = mCurrentDeviceState;
+ if (!mDeviceStateMapper.isDeviceStateValid(state)) {
return;
}
if (TextUtils.isEmpty(displayFeaturesString)) {
notifyDataChanged(new ArrayList<>());
} else {
- notifyDataChanged(calculateFoldingFeature(displayFeaturesString));
+ notifyDataChanged(calculateFoldingFeature(state, displayFeaturesString));
}
}
- private List<CommonFoldingFeature> calculateFoldingFeature(String displayFeaturesString) {
- return parseListFromString(displayFeaturesString, currentHingeState());
+ @NonNull
+ private List<CommonFoldingFeature> calculateFoldingFeature(@NonNull DeviceState deviceState,
+ @NonNull String displayFeaturesString) {
+ @CommonFoldingFeature.State
+ final int hingeState = mDeviceStateMapper.getHingeState(deviceState);
+ return parseListFromString(displayFeaturesString, hingeState);
}
- @CommonFoldingFeature.State
- private int currentHingeState() {
- @CommonFoldingFeature.State
- int posture = mDeviceStateToPostureMap.get(mCurrentDeviceState.getIdentifier(),
- COMMON_STATE_UNKNOWN);
+ /**
+ * Internal class to map device states to corresponding postures.
+ *
+ * <p>This class encapsulates the logic for mapping device states to postures. The mapping is
+ * immutable after initialization to ensure thread safety.
+ */
+ private static class DeviceStateMapper {
+ /**
+ * Emulated device state
+ * {@link DeviceStateManager.DeviceStateCallback#onDeviceStateChanged(DeviceState)} to
+ * {@link CommonFoldingFeature.State} map.
+ *
+ * <p>This map must be immutable after initialization to ensure thread safety, as it may be
+ * accessed from multiple threads. Modifications should only occur during object
+ * construction.
+ */
+ private final SparseIntArray mDeviceStateToPostureMap = new SparseIntArray();
+
+ /**
+ * The list of device states that are supported.
+ *
+ * <p>This list must be immutable after initialization to ensure thread safety.
+ */
+ @NonNull
+ private final List<DeviceState> mSupportedStates;
+
+ final boolean mIsHalfOpenedSupported;
+
+ DeviceStateMapper(@NonNull Context context, @NonNull List<DeviceState> supportedStates) {
+ mSupportedStates = supportedStates;
+
+ final String[] deviceStatePosturePairs = context.getResources()
+ .getStringArray(R.array.config_device_state_postures);
+ boolean isHalfOpenedSupported = false;
+ for (String deviceStatePosturePair : deviceStatePosturePairs) {
+ final String[] deviceStatePostureMapping = deviceStatePosturePair.split(":");
+ if (deviceStatePostureMapping.length != 2) {
+ if (DEBUG) {
+ Log.e(TAG, "Malformed device state posture pair: "
+ + deviceStatePosturePair);
+ }
+ continue;
+ }
- if (posture == CommonFoldingFeature.COMMON_STATE_USE_BASE_STATE) {
- posture = mDeviceStateToPostureMap.get(
- DeviceStateUtil.calculateBaseStateIdentifier(mCurrentDeviceState,
- mSupportedStates), COMMON_STATE_UNKNOWN);
+ final int deviceState;
+ final int posture;
+ try {
+ deviceState = Integer.parseInt(deviceStatePostureMapping[0]);
+ posture = Integer.parseInt(deviceStatePostureMapping[1]);
+ } catch (NumberFormatException e) {
+ if (DEBUG) {
+ Log.e(TAG, "Failed to parse device state or posture: "
+ + deviceStatePosturePair,
+ e);
+ }
+ continue;
+ }
+ isHalfOpenedSupported = isHalfOpenedSupported
+ || posture == CommonFoldingFeature.COMMON_STATE_HALF_OPENED;
+ mDeviceStateToPostureMap.put(deviceState, posture);
+ }
+ mIsHalfOpenedSupported = isHalfOpenedSupported;
+ }
+
+ boolean isDeviceStateToPostureMapEmpty() {
+ return mDeviceStateToPostureMap.size() == 0;
+ }
+
+ /**
+ * Validates if the provided deviceState exists in the {@link #mDeviceStateToPostureMap}
+ * which was initialized in the constructor of {@link DeviceStateMapper}.
+ * Returns a boolean value of whether the device state is valid.
+ */
+ boolean isDeviceStateValid(@NonNull DeviceState deviceState) {
+ // If the device state is not found in the map, indexOfKey returns a negative number.
+ return mDeviceStateToPostureMap.indexOfKey(deviceState.getIdentifier()) >= 0;
}
- return posture;
+ @CommonFoldingFeature.State
+ int getHingeState(@NonNull DeviceState deviceState) {
+ @CommonFoldingFeature.State
+ final int posture =
+ mDeviceStateToPostureMap.get(deviceState.getIdentifier(), COMMON_STATE_UNKNOWN);
+ if (posture != CommonFoldingFeature.COMMON_STATE_USE_BASE_STATE) {
+ return posture;
+ }
+
+ final int baseStateIdentifier =
+ DeviceStateUtil.calculateBaseStateIdentifier(deviceState, mSupportedStates);
+ return mDeviceStateToPostureMap.get(baseStateIdentifier, COMMON_STATE_UNKNOWN);
+ }
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/BackupHelper.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/BackupHelper.java
index bfccb29bc952..e3a1d8ac48e2 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/BackupHelper.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/BackupHelper.java
@@ -142,6 +142,19 @@ class BackupHelper {
}
}
+ void abortTaskContainerRebuilding(@NonNull WindowContainerTransaction wct) {
+ // Clean-up the legacy states in the system
+ for (int i = mTaskFragmentInfos.size() - 1; i >= 0; i--) {
+ final TaskFragmentInfo info = mTaskFragmentInfos.valueAt(i);
+ mPresenter.deleteTaskFragment(wct, info.getFragmentToken());
+ }
+ mPresenter.setSavedState(new Bundle());
+
+ mParcelableTaskContainerDataList.clear();
+ mTaskFragmentInfos.clear();
+ mTaskFragmentParentInfos.clear();
+ }
+
boolean hasPendingStateToRestore() {
return !mParcelableTaskContainerDataList.isEmpty();
}
@@ -196,6 +209,7 @@ class BackupHelper {
mController.onTaskFragmentParentRestored(wct, taskContainer.getTaskId(),
mTaskFragmentParentInfos.get(taskContainer.getTaskId()));
+ mTaskFragmentParentInfos.remove(taskContainer.getTaskId());
restoredAny = true;
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index db4bb0e5e75e..8345b409ae52 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -56,6 +56,7 @@ import static androidx.window.extensions.embedding.TaskFragmentContainer.Overlay
import android.annotation.CallbackExecutor;
import android.app.Activity;
import android.app.ActivityClient;
+import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.ActivityThread;
import android.app.AppGlobals;
@@ -280,7 +281,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
mSplitRules.clear();
mSplitRules.addAll(rules);
- if (!Flags.aeBackStackRestore() || !mPresenter.isRebuildTaskContainersNeeded()) {
+ if (!Flags.aeBackStackRestore() || !mPresenter.isWaitingToRebuildTaskContainers()) {
return;
}
@@ -2893,6 +2894,36 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
return;
}
synchronized (mLock) {
+ if (mPresenter.isWaitingToRebuildTaskContainers()) {
+ Log.w(TAG, "Rebuilding aborted, clean up and restart");
+
+ // Retrieve the Task intent.
+ final int taskId = getTaskId(activity);
+ Intent taskIntent = null;
+ final ActivityManager am = activity.getSystemService(ActivityManager.class);
+ final List<ActivityManager.AppTask> appTasks = am.getAppTasks();
+ for (ActivityManager.AppTask appTask : appTasks) {
+ if (appTask.getTaskInfo().taskId == taskId) {
+ taskIntent = appTask.getTaskInfo().baseIntent.cloneFilter();
+ break;
+ }
+ }
+
+ // Clean up and abort the restoration
+ // TODO(b/369488857): also to remove the non-organized activities in the Task?
+ final TransactionRecord transactionRecord =
+ mTransactionManager.startNewTransaction();
+ final WindowContainerTransaction wct = transactionRecord.getTransaction();
+ mPresenter.abortTaskContainerRebuilding(wct);
+ transactionRecord.apply(false /* shouldApplyIndependently */);
+
+ // Start the Task root activity.
+ if (taskIntent != null) {
+ activity.startActivity(taskIntent);
+ }
+ return;
+ }
+
final IBinder activityToken = activity.getActivityToken();
final IBinder initialTaskFragmentToken =
getTaskFragmentTokenFromActivityClientRecord(activity);
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index 0c0ded9bad74..b498ee2ff438 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -187,10 +187,14 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
mBackupHelper.scheduleBackup();
}
- boolean isRebuildTaskContainersNeeded() {
+ boolean isWaitingToRebuildTaskContainers() {
return mBackupHelper.hasPendingStateToRestore();
}
+ void abortTaskContainerRebuilding(@NonNull WindowContainerTransaction wct) {
+ mBackupHelper.abortTaskContainerRebuilding(wct);
+ }
+
boolean rebuildTaskContainers(@NonNull WindowContainerTransaction wct,
@NonNull Set<EmbeddingRule> rules) {
return mBackupHelper.rebuildTaskContainers(wct, rules);
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
index 74cce68f270b..b453f1d4e936 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -156,7 +156,7 @@ class TaskContainer {
mSplitController = splitController;
for (ParcelableTaskFragmentContainerData tfData :
data.getParcelableTaskFragmentContainerDataList()) {
- final TaskFragmentInfo info = taskFragmentInfoMap.get(tfData.mToken);
+ final TaskFragmentInfo info = taskFragmentInfoMap.remove(tfData.mToken);
if (info != null && !info.isEmpty()) {
final TaskFragmentContainer container =
new TaskFragmentContainer(tfData, splitController, this);
@@ -377,8 +377,16 @@ class TaskContainer {
@Nullable
TaskFragmentContainer getContainerWithActivity(@NonNull IBinder activityToken) {
- return getContainer(container -> container.hasAppearedActivity(activityToken)
- || container.hasPendingAppearedActivity(activityToken));
+ // When the new activity is launched to the topmost TF because the source activity
+ // was in that TF, and the source activity is finished before resolving the new activity,
+ // we will try to see if the new activity match a rule with the split activities below.
+ // If matched, it can be reparented.
+ final TaskFragmentContainer taskFragmentContainer
+ = getContainer(container -> container.hasPendingAppearedActivity(activityToken));
+ if (taskFragmentContainer != null) {
+ return taskFragmentContainer;
+ }
+ return getContainer(container -> container.hasAppearedActivity(activityToken));
}
@Nullable
diff --git a/libs/WindowManager/Jetpack/tests/unittest/Android.bp b/libs/WindowManager/Jetpack/tests/unittest/Android.bp
index bd430c0e610b..09185ee203b8 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/Android.bp
+++ b/libs/WindowManager/Jetpack/tests/unittest/Android.bp
@@ -29,6 +29,7 @@ android_test {
srcs: [
"**/*.java",
+ "**/*.kt",
],
static_libs: [
@@ -41,6 +42,7 @@ android_test {
"androidx.test.ext.junit",
"flag-junit",
"mockito-target-extended-minus-junit4",
+ "mockito-kotlin-nodeps",
"truth",
"testables",
"platform-test-annotations",
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducerTest.kt b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducerTest.kt
new file mode 100644
index 000000000000..90887a747a6f
--- /dev/null
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducerTest.kt
@@ -0,0 +1,341 @@
+/*
+ * 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 androidx.window.common
+
+import android.content.Context
+import android.content.res.Resources
+import android.hardware.devicestate.DeviceState
+import android.hardware.devicestate.DeviceStateManager
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.window.common.layout.CommonFoldingFeature
+import androidx.window.common.layout.CommonFoldingFeature.COMMON_STATE_FLAT
+import androidx.window.common.layout.CommonFoldingFeature.COMMON_STATE_HALF_OPENED
+import androidx.window.common.layout.CommonFoldingFeature.COMMON_STATE_NO_FOLDING_FEATURES
+import androidx.window.common.layout.CommonFoldingFeature.COMMON_STATE_UNKNOWN
+import androidx.window.common.layout.CommonFoldingFeature.COMMON_STATE_USE_BASE_STATE
+import androidx.window.common.layout.DisplayFoldFeatureCommon
+import androidx.window.common.layout.DisplayFoldFeatureCommon.DISPLAY_FOLD_FEATURE_PROPERTY_SUPPORTS_HALF_OPENED
+import androidx.window.common.layout.DisplayFoldFeatureCommon.DISPLAY_FOLD_FEATURE_TYPE_SCREEN_FOLD_IN
+import com.android.internal.R
+import com.android.window.flags.Flags
+import com.google.common.truth.Truth.assertThat
+import java.util.Optional
+import java.util.concurrent.Executor
+import java.util.function.Consumer
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.stub
+import org.mockito.kotlin.verify
+
+/**
+ * Test class for [DeviceStateManagerFoldingFeatureProducer].
+ *
+ * Build/Install/Run:
+ * atest WMJetpackUnitTests:DeviceStateManagerFoldingFeatureProducerTest
+ */
+@RunWith(AndroidJUnit4::class)
+class DeviceStateManagerFoldingFeatureProducerTest {
+ @get:Rule
+ val setFlagsRule: SetFlagsRule = SetFlagsRule()
+
+ private val mMockDeviceStateManager = mock<DeviceStateManager>()
+ private val mMockResources = mock<Resources> {
+ on { getStringArray(R.array.config_device_state_postures) } doReturn DEVICE_STATE_POSTURES
+ }
+ private val mMockContext = mock<Context> {
+ on { resources } doReturn mMockResources
+ }
+ private val mRawFoldSupplier = mock<RawFoldingFeatureProducer> {
+ on { currentData } doReturn Optional.of(DISPLAY_FEATURES)
+ on { getData(any<Consumer<String>>()) } doAnswer { invocation ->
+ val callback = invocation.getArgument(0) as Consumer<String>
+ callback.accept(DISPLAY_FEATURES)
+ }
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_WLINFO_ONCREATE)
+ fun testRegisterCallback_whenWlinfoOncreateIsDisabled_usesMainExecutor() {
+ DeviceStateManagerFoldingFeatureProducer(
+ mMockContext,
+ mRawFoldSupplier,
+ mMockDeviceStateManager,
+ )
+
+ verify(mMockDeviceStateManager).registerCallback(eq(mMockContext.mainExecutor), any())
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_WLINFO_ONCREATE)
+ fun testRegisterCallback_whenWlinfoOncreateIsEnabled_usesRunnableRun() {
+ val executorCaptor = ArgumentCaptor.forClass(Executor::class.java)
+ val runnable = mock<Runnable>()
+
+ DeviceStateManagerFoldingFeatureProducer(
+ mMockContext,
+ mRawFoldSupplier,
+ mMockDeviceStateManager,
+ )
+
+ verify(mMockDeviceStateManager).registerCallback(executorCaptor.capture(), any())
+ executorCaptor.value.execute(runnable)
+ verify(runnable).run()
+ }
+
+ @Test
+ fun testGetCurrentData_validCurrentState_returnsFoldingFeatureWithState() {
+ val ffp = DeviceStateManagerFoldingFeatureProducer(
+ mMockContext,
+ mRawFoldSupplier,
+ mMockDeviceStateManager,
+ )
+ ffp.mDeviceStateCallback.onDeviceStateChanged(DEVICE_STATE_HALF_OPENED)
+
+ val currentData = ffp.getCurrentData()
+
+ assertThat(currentData).isPresent()
+ assertThat(currentData.get()).containsExactlyElementsIn(HALF_OPENED_FOLDING_FEATURES)
+ }
+
+ @Test
+ fun testGetCurrentData_invalidCurrentState_returnsEmptyOptionalFoldingFeature() {
+ val ffp = DeviceStateManagerFoldingFeatureProducer(
+ mMockContext,
+ mRawFoldSupplier,
+ mMockDeviceStateManager,
+ )
+
+ val currentData = ffp.getCurrentData()
+
+ assertThat(currentData).isEmpty()
+ }
+
+ @Test
+ fun testGetFoldsWithUnknownState_validFoldingFeature_returnsFoldingFeaturesWithUnknownState() {
+ val ffp = DeviceStateManagerFoldingFeatureProducer(
+ mMockContext,
+ mRawFoldSupplier,
+ mMockDeviceStateManager,
+ )
+
+ val result = ffp.getFoldsWithUnknownState()
+
+ assertThat(result).containsExactlyElementsIn(UNKNOWN_STATE_FOLDING_FEATURES)
+ }
+
+ @Test
+ fun testGetFoldsWithUnknownState_emptyFoldingFeature_returnsEmptyList() {
+ mRawFoldSupplier.stub {
+ on { currentData } doReturn Optional.empty()
+ }
+ val ffp = DeviceStateManagerFoldingFeatureProducer(
+ mMockContext,
+ mRawFoldSupplier,
+ mMockDeviceStateManager,
+ )
+
+ val result = ffp.getFoldsWithUnknownState()
+
+ assertThat(result).isEmpty()
+ }
+
+ @Test
+ fun testGetDisplayFeatures_validFoldingFeature_returnsDisplayFoldFeatures() {
+ mRawFoldSupplier.stub {
+ on { currentData } doReturn Optional.of(DISPLAY_FEATURES_HALF_OPENED_HINGE)
+ }
+ val ffp = DeviceStateManagerFoldingFeatureProducer(
+ mMockContext,
+ mRawFoldSupplier,
+ mMockDeviceStateManager,
+ )
+
+ val result = ffp.displayFeatures
+
+ assertThat(result).containsExactly(
+ DisplayFoldFeatureCommon(
+ DISPLAY_FOLD_FEATURE_TYPE_SCREEN_FOLD_IN,
+ setOf(DISPLAY_FOLD_FEATURE_PROPERTY_SUPPORTS_HALF_OPENED),
+ ),
+ )
+ }
+
+ @Test
+ fun testIsHalfOpenedSupported_withHalfOpenedPostures_returnsTrue() {
+ val ffp = DeviceStateManagerFoldingFeatureProducer(
+ mMockContext,
+ mRawFoldSupplier,
+ mMockDeviceStateManager,
+ )
+
+ assertThat(ffp.isHalfOpenedSupported).isTrue()
+ }
+
+ @Test
+ fun testIsHalfOpenedSupported_withEmptyPostures_returnsFalse() {
+ mMockResources.stub {
+ on { getStringArray(R.array.config_device_state_postures) } doReturn emptyArray()
+ }
+ val ffp = DeviceStateManagerFoldingFeatureProducer(
+ mMockContext,
+ mRawFoldSupplier,
+ mMockDeviceStateManager,
+ )
+
+ assertThat(ffp.isHalfOpenedSupported).isFalse()
+ }
+
+ @Test
+ fun testGetData_emptyDisplayFeaturesString_callsConsumerWithEmptyList() {
+ mRawFoldSupplier.stub {
+ on { getData(any<Consumer<String>>()) } doAnswer { invocation ->
+ val callback = invocation.getArgument(0) as Consumer<String>
+ callback.accept("")
+ }
+ }
+ val ffp = DeviceStateManagerFoldingFeatureProducer(
+ mMockContext,
+ mRawFoldSupplier,
+ mMockDeviceStateManager,
+ )
+ val storeFeaturesConsumer = mock<Consumer<List<CommonFoldingFeature>>>()
+
+ ffp.getData(storeFeaturesConsumer)
+
+ verify(storeFeaturesConsumer).accept(emptyList())
+ }
+
+ @Test
+ fun testGetData_validState_callsConsumerWithFoldingFeatures() {
+ val ffp = DeviceStateManagerFoldingFeatureProducer(
+ mMockContext,
+ mRawFoldSupplier,
+ mMockDeviceStateManager,
+ )
+ ffp.mDeviceStateCallback.onDeviceStateChanged(DEVICE_STATE_HALF_OPENED)
+ val storeFeaturesConsumer = mock<Consumer<List<CommonFoldingFeature>>>()
+
+ ffp.getData(storeFeaturesConsumer)
+
+ verify(storeFeaturesConsumer).accept(HALF_OPENED_FOLDING_FEATURES)
+ }
+
+ @Test
+ fun testGetData_invalidState_addsAcceptOnceConsumerToDataChangedCallback() {
+ val ffp = DeviceStateManagerFoldingFeatureProducer(
+ mMockContext,
+ mRawFoldSupplier,
+ mMockDeviceStateManager,
+ )
+ val storeFeaturesConsumer = mock<Consumer<List<CommonFoldingFeature>>>()
+
+ ffp.getData(storeFeaturesConsumer)
+
+ verify(storeFeaturesConsumer, never()).accept(any())
+ ffp.mDeviceStateCallback.onDeviceStateChanged(DEVICE_STATE_HALF_OPENED)
+ ffp.mDeviceStateCallback.onDeviceStateChanged(DEVICE_STATE_OPENED)
+ verify(storeFeaturesConsumer).accept(HALF_OPENED_FOLDING_FEATURES)
+ }
+
+ @Test
+ fun testDeviceStateMapper_malformedDeviceStatePosturePair_skipsPair() {
+ val malformedDeviceStatePostures = arrayOf(
+ // Missing the posture.
+ "0",
+ // Empty string.
+ "",
+ // Too many elements.
+ "0:1:2",
+ )
+ mMockResources.stub {
+ on { getStringArray(R.array.config_device_state_postures) } doReturn
+ malformedDeviceStatePostures
+ }
+
+ DeviceStateManagerFoldingFeatureProducer(
+ mMockContext,
+ mRawFoldSupplier,
+ mMockDeviceStateManager,
+ )
+
+ verify(mMockDeviceStateManager, never()).registerCallback(any(), any())
+ }
+
+ @Test
+ fun testDeviceStateMapper_invalidNumberFormat_skipsPair() {
+ val invalidNumberFormatDeviceStatePostures = arrayOf("a:1", "0:b", "a:b", ":1")
+ mMockResources.stub {
+ on { getStringArray(R.array.config_device_state_postures) } doReturn
+ invalidNumberFormatDeviceStatePostures
+ }
+
+ DeviceStateManagerFoldingFeatureProducer(
+ mMockContext,
+ mRawFoldSupplier,
+ mMockDeviceStateManager,
+ )
+
+ verify(mMockDeviceStateManager, never()).registerCallback(any(), any())
+ }
+
+ companion object {
+ // Supported device states configuration.
+ private enum class SupportedDeviceStates {
+ CLOSED, HALF_OPENED, OPENED, REAR_DISPLAY, CONCURRENT;
+
+ override fun toString() = ordinal.toString()
+
+ fun toDeviceState(): DeviceState =
+ DeviceState(DeviceState.Configuration.Builder(ordinal, name).build())
+ }
+
+ // Map of supported device states supplied by DeviceStateManager to WM Jetpack posture.
+ private val DEVICE_STATE_POSTURES =
+ arrayOf(
+ "${SupportedDeviceStates.CLOSED}:$COMMON_STATE_NO_FOLDING_FEATURES",
+ "${SupportedDeviceStates.HALF_OPENED}:$COMMON_STATE_HALF_OPENED",
+ "${SupportedDeviceStates.OPENED}:$COMMON_STATE_FLAT",
+ "${SupportedDeviceStates.REAR_DISPLAY}:$COMMON_STATE_NO_FOLDING_FEATURES",
+ "${SupportedDeviceStates.CONCURRENT}:$COMMON_STATE_USE_BASE_STATE",
+ )
+ private val DEVICE_STATE_HALF_OPENED = SupportedDeviceStates.HALF_OPENED.toDeviceState()
+ private val DEVICE_STATE_OPENED = SupportedDeviceStates.OPENED.toDeviceState()
+
+ // WindowsManager Jetpack display features.
+ private val DISPLAY_FEATURES = "fold-[1104,0,1104,1848]"
+ private val DISPLAY_FEATURES_HALF_OPENED_HINGE = "$DISPLAY_FEATURES-half-opened"
+ private val HALF_OPENED_FOLDING_FEATURES = CommonFoldingFeature.parseListFromString(
+ DISPLAY_FEATURES,
+ COMMON_STATE_HALF_OPENED,
+ )
+ private val UNKNOWN_STATE_FOLDING_FEATURES = CommonFoldingFeature.parseListFromString(
+ DISPLAY_FEATURES,
+ COMMON_STATE_UNKNOWN,
+ )
+ }
+}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
index 7fab371cb790..bc4916a607a3 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
@@ -535,7 +535,8 @@ public class TaskFragmentContainerTest {
// container1.
container2.setInfo(mTransaction, mInfo);
- assertTrue(container2.hasActivity(mActivity.getActivityToken()));
+ assertTrue(container1.hasActivity(mActivity.getActivityToken()));
+ assertFalse(container2.hasActivity(mActivity.getActivityToken()));
// When the pending appeared record is removed from container1, we respect the appeared
// record in container2.
container1.removePendingAppearedActivity(mActivity.getActivityToken());
diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
index 63a288079401..cf0a975b6c30 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -156,6 +156,13 @@ flag {
}
flag {
+ name: "enable_flexible_two_app_split"
+ namespace: "multitasking"
+ description: "Enables only 2 app 90:10 split"
+ bug: "349828130"
+}
+
+flag {
name: "enable_flexible_split"
namespace: "multitasking"
description: "Enables flexibile split feature for split screen"
diff --git a/libs/WindowManager/Shell/res/drawable/app_handle_education_tooltip_icon.xml b/libs/WindowManager/Shell/res/drawable/app_handle_education_tooltip_icon.xml
new file mode 100644
index 000000000000..07e5ac1a604b
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/app_handle_education_tooltip_icon.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:tint="?android:attr/textColorTertiary"
+ android:viewportHeight="960"
+ android:viewportWidth="960">
+ <path
+ android:fillColor="@android:color/system_on_tertiary_fixed"
+ android:pathData="M419,880Q391,880 366.5,868Q342,856 325,834L107,557L126,537Q146,516 174,512Q202,508 226,523L300,568L300,240Q300,223 311.5,211.5Q323,200 340,200Q357,200 369,211.5Q381,223 381,240L381,712L284,652L388,785Q394,792 402,796Q410,800 419,800L640,800Q673,800 696.5,776.5Q720,753 720,720L720,560Q720,543 708.5,531.5Q697,520 680,520L461,520L461,440L680,440Q730,440 765,475Q800,510 800,560L800,720Q800,786 753,833Q706,880 640,880L419,880ZM167,340Q154,318 147,292.5Q140,267 140,240Q140,157 198.5,98.5Q257,40 340,40Q423,40 481.5,98.5Q540,157 540,240Q540,267 533,292.5Q526,318 513,340L444,300Q452,286 456,271.5Q460,257 460,240Q460,190 425,155Q390,120 340,120Q290,120 255,155Q220,190 220,240Q220,257 224,271.5Q228,286 236,300L167,340ZM502,620L502,620L502,620L502,620Q502,620 502,620Q502,620 502,620L502,620Q502,620 502,620Q502,620 502,620L502,620Q502,620 502,620Q502,620 502,620L502,620L502,620Z" />
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/desktop_windowing_education_tooltip_background.xml b/libs/WindowManager/Shell/res/drawable/desktop_windowing_education_tooltip_background.xml
new file mode 100644
index 000000000000..a12a74658953
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/desktop_windowing_education_tooltip_background.xml
@@ -0,0 +1,25 @@
+<?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.
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item>
+ <shape android:shape="rectangle">
+ <corners android:radius="30dp" />
+ <solid android:color="@android:color/system_tertiary_fixed" />
+ </shape>
+ </item>
+</layer-list>
diff --git a/libs/WindowManager/Shell/res/drawable/desktop_windowing_education_tooltip_left_arrow.xml b/libs/WindowManager/Shell/res/drawable/desktop_windowing_education_tooltip_left_arrow.xml
new file mode 100644
index 000000000000..aadffb5a0003
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/desktop_windowing_education_tooltip_left_arrow.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<!-- An arrow that points towards left. -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="10dp"
+ android:height="12dp"
+ android:viewportWidth="10"
+ android:viewportHeight="12">
+ <path
+ android:pathData="M2.858,4.285C1.564,5.062 1.564,6.938 2.858,7.715L10,12L10,0L2.858,4.285Z"
+ android:fillColor="@android:color/system_tertiary_fixed"/>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/desktop_windowing_education_tooltip_top_arrow.xml b/libs/WindowManager/Shell/res/drawable/desktop_windowing_education_tooltip_top_arrow.xml
new file mode 100644
index 000000000000..e3c9a662671e
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/desktop_windowing_education_tooltip_top_arrow.xml
@@ -0,0 +1,26 @@
+<!--
+ ~ 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.
+ -->
+
+<!-- An arrow that points upwards. -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="12dp"
+ android:height="9dp"
+ android:viewportWidth="12"
+ android:viewportHeight="9">
+ <path
+ android:pathData="M7.715,1.858C6.938,0.564 5.062,0.564 4.285,1.858L0,9L12,9L7.715,1.858Z"
+ android:fillColor="@android:color/system_tertiary_fixed"/>
+</vector>
diff --git a/libs/WindowManager/Shell/res/layout/desktop_windowing_education_left_arrow_tooltip.xml b/libs/WindowManager/Shell/res/layout/desktop_windowing_education_left_arrow_tooltip.xml
new file mode 100644
index 000000000000..a269b9ee1dd5
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/desktop_windowing_education_left_arrow_tooltip.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:elevation="1dp"
+ android:orientation="horizontal">
+
+ <!-- ImageView for the arrow icon, positioned horizontally at the start of the tooltip
+ container. -->
+ <ImageView
+ android:id="@+id/arrow_icon"
+ android:layout_width="10dp"
+ android:layout_height="12dp"
+ android:layout_gravity="center_vertical"
+ android:src="@drawable/desktop_windowing_education_tooltip_left_arrow" />
+
+ <!-- Layout for the tooltip, excluding the arrow. Separating the tooltip content from the arrow
+ allows scaling of only the tooltip container when the content changes, without affecting the
+ arrow. -->
+ <include layout="@layout/desktop_windowing_education_tooltip_container" />
+</LinearLayout>
diff --git a/libs/WindowManager/Shell/res/layout/desktop_windowing_education_tooltip_container.xml b/libs/WindowManager/Shell/res/layout/desktop_windowing_education_tooltip_container.xml
new file mode 100644
index 000000000000..bdee8836dc2e
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/desktop_windowing_education_tooltip_container.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/tooltip_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@drawable/desktop_windowing_education_tooltip_background"
+ android:orientation="horizontal"
+ android:padding="@dimen/desktop_windowing_education_tooltip_padding">
+
+ <ImageView
+ android:id="@+id/tooltip_icon"
+ android:layout_width="32dp"
+ android:layout_height="32dp"
+ android:layout_gravity="center_vertical"
+ android:src="@drawable/app_handle_education_tooltip_icon" />
+
+ <TextView
+ android:id="@+id/tooltip_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_marginStart="2dp"
+ android:lineHeight="20dp"
+ android:maxWidth="150dp"
+ android:textColor="@android:color/system_on_tertiary_fixed"
+ android:textFontWeight="500"
+ android:textSize="14sp" />
+</LinearLayout> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/desktop_windowing_education_top_arrow_tooltip.xml b/libs/WindowManager/Shell/res/layout/desktop_windowing_education_top_arrow_tooltip.xml
new file mode 100644
index 000000000000..c73c1dad0e18
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/desktop_windowing_education_top_arrow_tooltip.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:elevation="1dp"
+ android:orientation="vertical">
+
+ <!-- ImageView for the arrow icon, positioned vertically above the tooltip container. -->
+ <ImageView
+ android:id="@+id/arrow_icon"
+ android:layout_width="12dp"
+ android:layout_height="9dp"
+ android:layout_gravity="center_horizontal"
+ android:src="@drawable/desktop_windowing_education_tooltip_top_arrow" />
+
+ <!-- Layout for the tooltip, excluding the arrow. Separating the tooltip content from the arrow
+ allows scaling of only the tooltip container when the content changes, without affecting the
+ arrow. -->
+ <include layout="@layout/desktop_windowing_education_tooltip_container" />
+</LinearLayout>
diff --git a/libs/WindowManager/Shell/res/layout/letterbox_restart_dialog_layout.xml b/libs/WindowManager/Shell/res/layout/letterbox_restart_dialog_layout.xml
index 045b975a854e..462a49ccb1eb 100644
--- a/libs/WindowManager/Shell/res/layout/letterbox_restart_dialog_layout.xml
+++ b/libs/WindowManager/Shell/res/layout/letterbox_restart_dialog_layout.xml
@@ -99,11 +99,11 @@
</LinearLayout>
- <FrameLayout
+
+ <LinearLayout
android:minHeight="@dimen/letterbox_restart_dialog_button_height"
- android:layout_width="match_parent"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
- style="?android:attr/buttonBarButtonStyle"
android:layout_gravity="end">
<Button
@@ -133,7 +133,7 @@
android:text="@string/letterbox_restart_restart"
android:contentDescription="@string/letterbox_restart_restart"/>
- </FrameLayout>
+ </LinearLayout>
</LinearLayout>
diff --git a/libs/WindowManager/Shell/res/values-bg/strings.xml b/libs/WindowManager/Shell/res/values-bg/strings.xml
index b385d285350b..f5e94da56055 100644
--- a/libs/WindowManager/Shell/res/values-bg/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bg/strings.xml
@@ -126,15 +126,11 @@
<string name="manage_windows_text" msgid="5567366688493093920">"Управление на прозорците"</string>
<string name="close_text" msgid="4986518933445178928">"Затваряне"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Затваряне на менюто"</string>
- <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) -->
- <skip />
+ <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Отваряне на менюто"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Увеличаване на екрана"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Прилепване на екрана"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Това приложение не може да бъде преоразмерено"</string>
- <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Увеличаване"</string>
+ <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Прилепване наляво"</string>
+ <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Прилепване надясно"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-bn/strings.xml b/libs/WindowManager/Shell/res/values-bn/strings.xml
index 56fcf7f4fff5..5d6374403662 100644
--- a/libs/WindowManager/Shell/res/values-bn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bn/strings.xml
@@ -126,15 +126,11 @@
<string name="manage_windows_text" msgid="5567366688493093920">"উইন্ডো ম্যানেজ করুন"</string>
<string name="close_text" msgid="4986518933445178928">"বন্ধ করুন"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"\'মেনু\' বন্ধ করুন"</string>
- <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) -->
- <skip />
+ <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"মেনু খুলুন"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"স্ক্রিন বড় করুন"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"স্ক্রিনে অ্যাপ মানানসই হিসেবে ছোট বড় করুন"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"এই অ্যাপ ছোট বড় করা যাবে না"</string>
- <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"বড় করুন"</string>
+ <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"বাঁদিকে স্ন্যাপ করুন"</string>
+ <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"ডানদিকে স্ন্যাপ করুন"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-cs/strings.xml b/libs/WindowManager/Shell/res/values-cs/strings.xml
index 3aae52e4808a..716491020c65 100644
--- a/libs/WindowManager/Shell/res/values-cs/strings.xml
+++ b/libs/WindowManager/Shell/res/values-cs/strings.xml
@@ -126,15 +126,11 @@
<string name="manage_windows_text" msgid="5567366688493093920">"Spravovat okna"</string>
<string name="close_text" msgid="4986518933445178928">"Zavřít"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Zavřít nabídku"</string>
- <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) -->
- <skip />
+ <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Otevřít nabídku"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximalizovat obrazovku"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Rozpůlit obrazovku"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Velikost aplikace nelze změnit"</string>
- <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maximalizovat"</string>
+ <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Přichytit vlevo"</string>
+ <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Přichytit vpravo"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
index e4ece9386a3f..766852d4b1f0 100644
--- a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
@@ -126,15 +126,11 @@
<string name="manage_windows_text" msgid="5567366688493093920">"Manage windows"</string>
<string name="close_text" msgid="4986518933445178928">"Close"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Close menu"</string>
- <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) -->
- <skip />
+ <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Open menu"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximise screen"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Snap screen"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"This app can\'t be resized"</string>
- <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maximise"</string>
+ <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Snap left"</string>
+ <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Snap right"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
index 14fe60b3e3cb..aa3a484079f8 100644
--- a/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rCA/strings.xml
@@ -126,15 +126,11 @@
<string name="manage_windows_text" msgid="5567366688493093920">"Manage Windows"</string>
<string name="close_text" msgid="4986518933445178928">"Close"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Close Menu"</string>
- <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) -->
- <skip />
+ <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Open Menu"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximize Screen"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Snap Screen"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"This app can\'t be resized"</string>
- <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maximize"</string>
+ <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Snap left"</string>
+ <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Snap right"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
index e4ece9386a3f..766852d4b1f0 100644
--- a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
@@ -126,15 +126,11 @@
<string name="manage_windows_text" msgid="5567366688493093920">"Manage windows"</string>
<string name="close_text" msgid="4986518933445178928">"Close"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Close menu"</string>
- <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) -->
- <skip />
+ <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Open menu"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximise screen"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Snap screen"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"This app can\'t be resized"</string>
- <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maximise"</string>
+ <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Snap left"</string>
+ <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Snap right"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
index e4ece9386a3f..766852d4b1f0 100644
--- a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
@@ -126,15 +126,11 @@
<string name="manage_windows_text" msgid="5567366688493093920">"Manage windows"</string>
<string name="close_text" msgid="4986518933445178928">"Close"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Close menu"</string>
- <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) -->
- <skip />
+ <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Open menu"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximise screen"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Snap screen"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"This app can\'t be resized"</string>
- <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maximise"</string>
+ <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Snap left"</string>
+ <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Snap right"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-en-rXC/strings.xml b/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
index dda97abf7b03..bda5132156cd 100644
--- a/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rXC/strings.xml
@@ -126,15 +126,11 @@
<string name="manage_windows_text" msgid="5567366688493093920">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‎‏‎‏‎‎‎‎‏‏‎‏‎‎‎‎‏‎‏‎‎‎‎‎‎‏‏‎‎‎‏‎‎‏‏‏‏‏‏‎‎‏‎‏‎‎‎‏‎‎‎‎‏‎‎‎‎‎‎Manage Windows‎‏‎‎‏‎"</string>
<string name="close_text" msgid="4986518933445178928">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‎‏‎‎‏‏‎‎‏‏‏‎‏‎‏‏‎‎‏‎‎‎‏‏‎‎‏‏‎‏‏‏‎‏‏‏‎‎‎‎‏‎‎‏‏‏‎‏‏‎‎‎‏‏‎‎‎‎‎Close‎‏‎‎‏‎"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‎‎‎‏‎‎‏‎‏‎‏‎‏‎‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‏‎‏‏‎‏‏‎‏‎‎‏‏‏‏‏‏‎‎‎‎‏‎‎‎‏‏‎‏‎Close Menu‎‏‎‎‏‎"</string>
- <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) -->
- <skip />
+ <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‎‎‏‎‏‏‎‏‎‎‎‎‏‎‎‏‏‏‎‏‎‎‎‏‏‎‎‏‏‏‎‎‎‏‎‎‏‎‏‏‏‏‏‎‎‏‏‏‎‎‏‎‎‎‏‎‏‎Open Menu‎‏‎‎‏‎"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‎‏‎‏‏‏‎‏‎‏‏‎‏‎‏‏‏‏‏‎‎‎‎‎‎‏‏‏‎‏‎‏‏‎‏‏‎‎‏‎‏‎‎‎‎‏‎‏‏‏‏‎‏‎‏‎‏‏‎Maximize Screen‎‏‎‎‏‎"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‎‎‏‎‏‎‏‎‎‏‎‎‏‏‏‏‏‎‏‏‎‏‏‏‎‎‏‏‏‏‎‎‎‏‎‎‎‎‏‎‏‏‏‎‎‏‏‎‏‏‏‏‎‏‏‎‏‎‎Snap Screen‎‏‎‎‏‎"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‎‏‎‎‏‎‎‎‏‏‎‏‎‎‎‏‏‏‏‏‏‎‎‎‏‏‎‏‎‎‎‏‎‏‎‎‎‏‏‏‏‏‎‎‏‎‏‎‎‏‎‎‎‎‎‏‏‎This app can\'t be resized‎‏‎‎‏‎"</string>
- <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‎‏‏‏‎‎‎‏‎‏‎‎‏‎‏‏‏‏‏‎‎‎‏‏‏‎‎‎‏‎‎‎‎‏‎‏‏‎‏‎‏‎‎‏‎‏‎‎‏‏‏‏‎‎‏‎‏‎Maximize‎‏‎‎‏‎"</string>
+ <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‎‎‎‎‎‏‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‎‏‎‏‎‏‏‎‎‎‎‏‏‎‎‏‎‎‏‏‏‎‏‎‏‏‎‎‎‎‎Snap left‎‏‎‎‏‎"</string>
+ <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‏‎‏‏‎‎‎‏‏‏‎‏‎‏‎‏‎‎‏‏‏‎‎‎‏‎‏‏‎‎‎‎‎‎‎‎‏‎‏‎‎‏‏‎‏‎‏‏‎‏‏‏‎‏‏‎‎‎‎Snap right‎‏‎‎‏‎"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-fa/strings_tv.xml b/libs/WindowManager/Shell/res/values-fa/strings_tv.xml
index 55394cbdc31a..7b0c8c6c923d 100644
--- a/libs/WindowManager/Shell/res/values-fa/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-fa/strings_tv.xml
@@ -18,7 +18,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="notification_channel_tv_pip" msgid="2576686079160402435">"تصویر در تصویر"</string>
- <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(برنامه بدون عنوان)"</string>
+ <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(برنامه بی‌عنوان)"</string>
<string name="pip_close" msgid="2955969519031223530">"بستن"</string>
<string name="pip_fullscreen" msgid="7278047353591302554">"تمام صفحه"</string>
<string name="pip_move" msgid="158770205886688553">"انتقال"</string>
diff --git a/libs/WindowManager/Shell/res/values-hi/strings.xml b/libs/WindowManager/Shell/res/values-hi/strings.xml
index d6fc85b6cdcc..37608207b721 100644
--- a/libs/WindowManager/Shell/res/values-hi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hi/strings.xml
@@ -126,15 +126,11 @@
<string name="manage_windows_text" msgid="5567366688493093920">"विंडो मैनेज करें"</string>
<string name="close_text" msgid="4986518933445178928">"बंद करें"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"मेन्यू बंद करें"</string>
- <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) -->
- <skip />
+ <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"मेन्यू खोलें"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"स्क्रीन को बड़ा करें"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"स्नैप स्क्रीन"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"इस ऐप्लिकेशन का साइज़ नहीं बदला जा सकता"</string>
- <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"बड़ा करें"</string>
+ <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"बाईं ओर स्नैप करें"</string>
+ <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"दाईं ओर स्नैप करें"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-hu/strings.xml b/libs/WindowManager/Shell/res/values-hu/strings.xml
index aaa4e2654f39..fb44cd855a7b 100644
--- a/libs/WindowManager/Shell/res/values-hu/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hu/strings.xml
@@ -126,15 +126,11 @@
<string name="manage_windows_text" msgid="5567366688493093920">"Ablakok kezelése"</string>
<string name="close_text" msgid="4986518933445178928">"Bezárás"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Menü bezárása"</string>
- <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) -->
- <skip />
+ <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Menü megnyitása"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Képernyő méretének maximalizálása"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Igazodás a képernyő adott részéhez"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Ezt az alkalmazást nem lehet átméretezni"</string>
- <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Teljes méret"</string>
+ <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Balra igazítás"</string>
+ <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Jobbra igazítás"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ja/strings.xml b/libs/WindowManager/Shell/res/values-ja/strings.xml
index 5d8dc5e85462..1225b62c96d9 100644
--- a/libs/WindowManager/Shell/res/values-ja/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ja/strings.xml
@@ -126,15 +126,11 @@
<string name="manage_windows_text" msgid="5567366688493093920">"ウィンドウを管理する"</string>
<string name="close_text" msgid="4986518933445178928">"閉じる"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"メニューを閉じる"</string>
- <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) -->
- <skip />
+ <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"メニューを開く"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"画面の最大化"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"画面のスナップ"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"このアプリはサイズ変更できません"</string>
- <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"最大化"</string>
+ <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"左にスナップ"</string>
+ <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"右にスナップ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ka/strings.xml b/libs/WindowManager/Shell/res/values-ka/strings.xml
index de08b9ca2d7e..3e1f726ca0ef 100644
--- a/libs/WindowManager/Shell/res/values-ka/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ka/strings.xml
@@ -126,15 +126,11 @@
<string name="manage_windows_text" msgid="5567366688493093920">"ფანჯრების მართვა"</string>
<string name="close_text" msgid="4986518933445178928">"დახურვა"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"მენიუს დახურვა"</string>
- <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) -->
- <skip />
+ <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"მენიუს გახსნა"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"აპლიკაციის გაშლა სრულ ეკრანზე"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"აპლიკაციის დაპატარავება ეკრანზე"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"აპის ზომის შეცვლა შეუძლებელია"</string>
- <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"მაქსიმალურად გაშლა"</string>
+ <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"მარცხნივ გადატანა"</string>
+ <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"მარჯვნივ გადატანა"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-lo/strings.xml b/libs/WindowManager/Shell/res/values-lo/strings.xml
index 5bc5316494b3..4e10621c719b 100644
--- a/libs/WindowManager/Shell/res/values-lo/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lo/strings.xml
@@ -126,15 +126,11 @@
<string name="manage_windows_text" msgid="5567366688493093920">"ຈັດການໜ້າຈໍ"</string>
<string name="close_text" msgid="4986518933445178928">"ປິດ"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"ປິດເມນູ"</string>
- <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) -->
- <skip />
+ <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"ເປີດເມນູ"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"ປັບຈໍໃຫຍ່ສຸດ"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"ສະແນັບໜ້າຈໍ"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"ບໍ່ສາມາດປັບຂະໜາດແອັບນີ້ໄດ້"</string>
- <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"ຂະຫຍາຍໃຫຍ່ສຸດ"</string>
+ <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"ແນບຊ້າຍ"</string>
+ <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"ແນບຂວາ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-lt/strings.xml b/libs/WindowManager/Shell/res/values-lt/strings.xml
index 01a72ee4cced..6d3c58cf01bb 100644
--- a/libs/WindowManager/Shell/res/values-lt/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lt/strings.xml
@@ -126,15 +126,11 @@
<string name="manage_windows_text" msgid="5567366688493093920">"Tvarkyti langus"</string>
<string name="close_text" msgid="4986518933445178928">"Uždaryti"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Uždaryti meniu"</string>
- <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) -->
- <skip />
+ <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Atidaryti meniu"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Išskleisti ekraną"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Sutraukti ekraną"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Negalima keisti šios programos dydžio"</string>
- <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Padidinti"</string>
+ <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Pritraukti kairėje"</string>
+ <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Pritraukti dešinėje"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ml/strings.xml b/libs/WindowManager/Shell/res/values-ml/strings.xml
index d51c3fb70a9e..a48df0b71b1b 100644
--- a/libs/WindowManager/Shell/res/values-ml/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ml/strings.xml
@@ -126,15 +126,11 @@
<string name="manage_windows_text" msgid="5567366688493093920">"വിൻഡോകൾ മാനേജ് ചെയ്യുക"</string>
<string name="close_text" msgid="4986518933445178928">"അടയ്ക്കുക"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"മെനു അടയ്ക്കുക"</string>
- <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) -->
- <skip />
+ <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"മെനു തുറക്കുക"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"സ്‌ക്രീൻ വലുതാക്കുക"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"സ്‌ക്രീൻ സ്‌നാപ്പ് ചെയ്യുക"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"ഈ ആപ്പിന്റെ വലുപ്പം മാറ്റാനാകില്ല"</string>
- <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"വലുതാക്കുക"</string>
+ <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"ഇടതുവശത്തേക്ക് സ്‌നാപ്പ് ചെയ്യുക"</string>
+ <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"വലതുവശത്തേക്ക് സ്‌നാപ്പ് ചെയ്യുക"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ms/strings.xml b/libs/WindowManager/Shell/res/values-ms/strings.xml
index e510d723f1e5..15ccfbbad39f 100644
--- a/libs/WindowManager/Shell/res/values-ms/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ms/strings.xml
@@ -126,15 +126,11 @@
<string name="manage_windows_text" msgid="5567366688493093920">"Urus Tetingkap"</string>
<string name="close_text" msgid="4986518933445178928">"Tutup"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Tutup Menu"</string>
- <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) -->
- <skip />
+ <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Buka Menu"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimumkan Skrin"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Tangkap Skrin"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Apl ini tidak boleh diubah saiz"</string>
- <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maksimumkan"</string>
+ <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Autojajar ke kiri"</string>
+ <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Autojajar ke kanan"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-pa/strings.xml b/libs/WindowManager/Shell/res/values-pa/strings.xml
index 5a4e61a2303b..e11cc1c1f76d 100644
--- a/libs/WindowManager/Shell/res/values-pa/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pa/strings.xml
@@ -126,15 +126,11 @@
<string name="manage_windows_text" msgid="5567366688493093920">"ਵਿੰਡੋਆਂ ਦਾ ਪ੍ਰਬੰਧਨ ਕਰੋ"</string>
<string name="close_text" msgid="4986518933445178928">"ਬੰਦ ਕਰੋ"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"ਮੀਨੂ ਬੰਦ ਕਰੋ"</string>
- <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) -->
- <skip />
+ <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"ਮੀਨੂ ਖੋਲ੍ਹੋ"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"ਸਕ੍ਰੀਨ ਦਾ ਆਕਾਰ ਵਧਾਓ"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"ਸਕ੍ਰੀਨ ਨੂੰ ਸਨੈਪ ਕਰੋ"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"ਇਸ ਐਪ ਦਾ ਆਕਾਰ ਬਦਲਿਆ ਨਹੀਂ ਜਾ ਸਕਦਾ"</string>
- <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"ਵੱਡਾ ਕਰੋ"</string>
+ <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"ਖੱਬੇ ਪਾਸੇ ਸਨੈਪ ਕਰੋ"</string>
+ <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"ਸੱਜੇ ਪਾਸੇ ਸਨੈਪ ਕਰੋ"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-pl/strings.xml b/libs/WindowManager/Shell/res/values-pl/strings.xml
index a14bb99286f8..2640c0f368a8 100644
--- a/libs/WindowManager/Shell/res/values-pl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pl/strings.xml
@@ -126,15 +126,11 @@
<string name="manage_windows_text" msgid="5567366688493093920">"Zarządzaj oknami"</string>
<string name="close_text" msgid="4986518933445178928">"Zamknij"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Zamknij menu"</string>
- <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) -->
- <skip />
+ <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Otwórz menu"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksymalizuj ekran"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Przyciągnij ekran"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Nie można zmienić rozmiaru tej aplikacji"</string>
- <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maksymalizuj"</string>
+ <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Przyciągnij do lewej"</string>
+ <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Przyciągnij do prawej"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
index 1d31d0d92309..46f8b38dd621 100644
--- a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
@@ -126,15 +126,11 @@
<string name="manage_windows_text" msgid="5567366688493093920">"Faça a gestão das janelas"</string>
<string name="close_text" msgid="4986518933445178928">"Fechar"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Fechar menu"</string>
- <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) -->
- <skip />
+ <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Abrir menu"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximizar ecrã"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Encaixar ecrã"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Não é possível redimensionar esta app"</string>
- <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maximizar"</string>
+ <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Encaixar à esquerda"</string>
+ <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Encaixar à direita"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-ru/strings.xml b/libs/WindowManager/Shell/res/values-ru/strings.xml
index a6c20adc4c2e..affd0cb0853a 100644
--- a/libs/WindowManager/Shell/res/values-ru/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ru/strings.xml
@@ -126,15 +126,11 @@
<string name="manage_windows_text" msgid="5567366688493093920">"Управление окнами"</string>
<string name="close_text" msgid="4986518933445178928">"Закрыть"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Закрыть меню"</string>
- <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) -->
- <skip />
+ <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Открыть меню"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Развернуть на весь экран"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Свернуть"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Изменить размер приложения нельзя."</string>
- <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Развернуть"</string>
+ <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Привязать слева"</string>
+ <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Привязать справа"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sl/strings.xml b/libs/WindowManager/Shell/res/values-sl/strings.xml
index fe08e04b21a3..280346d0559a 100644
--- a/libs/WindowManager/Shell/res/values-sl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sl/strings.xml
@@ -126,15 +126,11 @@
<string name="manage_windows_text" msgid="5567366688493093920">"Upravljanje oken"</string>
<string name="close_text" msgid="4986518933445178928">"Zapri"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Zapri meni"</string>
- <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) -->
- <skip />
+ <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Odpri meni"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimiraj zaslon"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Pripni zaslon"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Velikosti te aplikacije ni mogoče spremeniti"</string>
- <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maksimiraj"</string>
+ <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Pripni levo"</string>
+ <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Pripni desno"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-sv/strings.xml b/libs/WindowManager/Shell/res/values-sv/strings.xml
index c43f5dfa7b02..aa74bdef0140 100644
--- a/libs/WindowManager/Shell/res/values-sv/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sv/strings.xml
@@ -126,15 +126,11 @@
<string name="manage_windows_text" msgid="5567366688493093920">"Hantera fönster"</string>
<string name="close_text" msgid="4986518933445178928">"Stäng"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Stäng menyn"</string>
- <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) -->
- <skip />
+ <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Öppna menyn"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximera skärmen"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Fäst skärmen"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Det går inte att ändra storlek på appen"</string>
- <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Utöka"</string>
+ <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Fäst till vänster"</string>
+ <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Fäst till höger"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values-uz/strings.xml b/libs/WindowManager/Shell/res/values-uz/strings.xml
index 8754629df761..cd0ca82ff563 100644
--- a/libs/WindowManager/Shell/res/values-uz/strings.xml
+++ b/libs/WindowManager/Shell/res/values-uz/strings.xml
@@ -126,15 +126,11 @@
<string name="manage_windows_text" msgid="5567366688493093920">"Oynalarni boshqarish"</string>
<string name="close_text" msgid="4986518933445178928">"Yopish"</string>
<string name="collapse_menu_text" msgid="7515008122450342029">"Menyuni yopish"</string>
- <!-- no translation found for desktop_mode_app_header_chip_text (6366422614991687237) -->
- <skip />
+ <string name="desktop_mode_app_header_chip_text" msgid="6366422614991687237">"Menyuni ochish"</string>
<string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Ekranni yoyish"</string>
<string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Ekranni biriktirish"</string>
<string name="desktop_mode_non_resizable_snap_text" msgid="1049800446363800707">"Bu ilova hajmini oʻzgartirish imkonsiz"</string>
- <!-- no translation found for desktop_mode_maximize_menu_maximize_button_text (3090199175564175845) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_snap_left_button_text (8077452201179893424) -->
- <skip />
- <!-- no translation found for desktop_mode_maximize_menu_snap_right_button_text (7117751068945657304) -->
- <skip />
+ <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Yoyish"</string>
+ <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Chapga tortish"</string>
+ <string name="desktop_mode_maximize_menu_snap_right_button_text" msgid="7117751068945657304">"Oʻngga tortish"</string>
</resources>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 3d8718332199..c7109f5be132 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -608,6 +608,9 @@
<!-- The horizontal inset to apply to the close button's ripple drawable -->
<dimen name="desktop_mode_header_close_ripple_inset_horizontal">6dp</dimen>
+ <!-- The padding added to all sides of windowing education tooltip -->
+ <dimen name="desktop_windowing_education_tooltip_padding">8dp</dimen>
+
<!-- The acceptable area ratio of fg icon area/bg icon area, i.e. (72 x 72) / (108 x 108) -->
<item type="dimen" format="float" name="splash_icon_enlarge_foreground_threshold">0.44</item>
<!-- Scaling factor applied to splash icons without provided background i.e. (192 / 160) -->
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index bda56860d3ba..56f25dae3df2 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -219,6 +219,15 @@
compatibility control. [CHAR LIMIT=NONE] -->
<string name="camera_compat_dismiss_button_description">No camera issues? Tap to dismiss.</string>
+ <!-- App handle education tooltip text for tooltip pointing to app handle -->
+ <string name="windowing_app_handle_education_tooltip">Tap to open the app menu</string>
+
+ <!-- App handle education tooltip text for tooltip pointing to windowing image button -->
+ <string name="windowing_desktop_mode_image_button_education_tooltip">Tap to show multiple apps together</string>
+
+ <!-- App handle education tooltip text for tooltip pointing to app chip -->
+ <string name="windowing_desktop_mode_exit_education_tooltip">Return to fullscreen from the app menu</string>
+
<!-- The title of the letterbox education dialog. [CHAR LIMIT=NONE] -->
<string name="letterbox_education_dialog_title">See and do more</string>
@@ -307,12 +316,11 @@
<!-- Maximize menu snap buttons string. -->
<string name="desktop_mode_maximize_menu_snap_text">Snap Screen</string>
<!-- Snap resizing non-resizable string. -->
- <string name="desktop_mode_non_resizable_snap_text">This app can\'t be resized</string>
+ <string name="desktop_mode_non_resizable_snap_text">App can\'t be moved here</string>
<!-- Accessibility text for the Maximize Menu's maximize button [CHAR LIMIT=NONE] -->
<string name="desktop_mode_maximize_menu_maximize_button_text">Maximize</string>
<!-- Accessibility text for the Maximize Menu's snap left button [CHAR LIMIT=NONE] -->
<string name="desktop_mode_maximize_menu_snap_left_button_text">Snap left</string>
<!-- Accessibility text for the Maximize Menu's snap right button [CHAR LIMIT=NONE] -->
<string name="desktop_mode_maximize_menu_snap_right_button_text">Snap right</string>
-
</resources>
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/FocusTransitionListener.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/FocusTransitionListener.java
new file mode 100644
index 000000000000..26aae2d2aa78
--- /dev/null
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/FocusTransitionListener.java
@@ -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.wm.shell.shared;
+
+import com.android.wm.shell.shared.annotations.ExternalThread;
+
+/**
+ * Listener to get focus-related transition callbacks.
+ */
+@ExternalThread
+public interface FocusTransitionListener {
+ /**
+ * Called when a transition changes the top, focused display.
+ */
+ void onFocusedDisplayChanged(int displayId);
+}
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/IFocusTransitionListener.aidl b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/IFocusTransitionListener.aidl
new file mode 100644
index 000000000000..b91d5b6e2769
--- /dev/null
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/IFocusTransitionListener.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.shared;
+
+/**
+ * Listener interface that to get focus-related transition callbacks.
+ */
+oneway interface IFocusTransitionListener {
+
+ /**
+ * Called when a transition changes the top, focused display.
+ */
+ void onFocusedDisplayChanged(int displayId);
+}
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/IShellTransitions.aidl b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/IShellTransitions.aidl
index 3256abf09116..02615a96a86c 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/IShellTransitions.aidl
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/IShellTransitions.aidl
@@ -20,6 +20,7 @@ import android.view.SurfaceControl;
import android.window.RemoteTransition;
import android.window.TransitionFilter;
+import com.android.wm.shell.shared.IFocusTransitionListener;
import com.android.wm.shell.shared.IHomeTransitionListener;
/**
@@ -59,4 +60,9 @@ interface IShellTransitions {
*/
oneway void registerRemoteForTakeover(in TransitionFilter filter,
in RemoteTransition remoteTransition) = 6;
+
+ /**
+ * Set listener that will receive callbacks about transitions involving focus switch.
+ */
+ oneway void setFocusTransitionListener(in IFocusTransitionListener listener) = 7;
}
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/ShellTransitions.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/ShellTransitions.java
index 6d4ab4c1bd09..2db4311fb771 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/ShellTransitions.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/ShellTransitions.java
@@ -22,6 +22,8 @@ import android.window.TransitionFilter;
import com.android.wm.shell.shared.annotations.ExternalThread;
+import java.util.concurrent.Executor;
+
/**
* Interface to manage remote transitions.
*/
@@ -44,4 +46,15 @@ public interface ShellTransitions {
* Unregisters a remote transition for all operations.
*/
default void unregisterRemote(@NonNull RemoteTransition remoteTransition) {}
+
+ /**
+ * Sets listener that will receive callbacks about transitions involving focus switch.
+ */
+ default void setFocusTransitionListener(@NonNull FocusTransitionListener listener,
+ Executor executor) {}
+
+ /**
+ * Unsets listener that will receive callbacks about transitions involving focus switch.
+ */
+ default void unsetFocusTransitionListener(@NonNull FocusTransitionListener listener) {}
}
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/OWNERS b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/OWNERS
new file mode 100644
index 000000000000..bfb6d4ac5849
--- /dev/null
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/OWNERS
@@ -0,0 +1,4 @@
+jeremysim@google.com
+winsonc@google.com
+peanutbutter@google.com
+shuminghao@google.com
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitScreenConstants.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitScreenConstants.java
index 498dc8bdd24d..7f1e4a873f64 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitScreenConstants.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitScreenConstants.java
@@ -66,14 +66,54 @@ public class SplitScreenConstants {
public @interface SplitPosition {
}
- /** A snap target in the first half of the screen, where the split is roughly 30-70. */
- public static final int SNAP_TO_30_70 = 0;
+ /**
+ * A snap target for two apps, where the split is 33-66. With FLAG_ENABLE_FLEXIBLE_SPLIT,
+ * only used on tablets.
+ */
+ public static final int SNAP_TO_2_33_66 = 0;
+
+ /** A snap target for two apps, where the split is 50-50. */
+ public static final int SNAP_TO_2_50_50 = 1;
+
+ /**
+ * A snap target for two apps, where the split is 66-33. With FLAG_ENABLE_FLEXIBLE_SPLIT,
+ * only used on tablets.
+ */
+ public static final int SNAP_TO_2_66_33 = 2;
- /** The 50-50 snap target */
- public static final int SNAP_TO_50_50 = 1;
+ /**
+ * A snap target for two apps, where the split is 90-10. The "10" app extends off the screen,
+ * and is actually the same size as the onscreen app, but the visible portion takes up 10% of
+ * the screen. With FLAG_ENABLE_FLEXIBLE_SPLIT, used on phones and foldables.
+ */
+ public static final int SNAP_TO_2_90_10 = 3;
- /** A snap target in the latter half of the screen, where the split is roughly 70-30. */
- public static final int SNAP_TO_70_30 = 2;
+ /**
+ * A snap target for two apps, where the split is 10-90. The "10" app extends off the screen,
+ * and is actually the same size as the onscreen app, but the visible portion takes up 10% of
+ * the screen. With FLAG_ENABLE_FLEXIBLE_SPLIT, used on phones and foldables.
+ */
+ public static final int SNAP_TO_2_10_90 = 4;
+
+ /**
+ * A snap target for three apps, where the split is 33-33-33. With FLAG_ENABLE_FLEXIBLE_SPLIT,
+ * only used on tablets.
+ */
+ public static final int SNAP_TO_3_33_33_33 = 5;
+
+ /**
+ * A snap target for three apps, where the split is 45-45-10. The "10" app extends off the
+ * screen, and is actually the same size as the onscreen apps, but the visible portion takes
+ * up 10% of the screen. With FLAG_ENABLE_FLEXIBLE_SPLIT, only used on unfolded foldables.
+ */
+ public static final int SNAP_TO_3_45_45_10 = 6;
+
+ /**
+ * A snap target for three apps, where the split is 10-45-45. The "10" app extends off the
+ * screen, and is actually the same size as the onscreen apps, but the visible portion takes
+ * up 10% of the screen. With FLAG_ENABLE_FLEXIBLE_SPLIT, only used on unfolded foldables.
+ */
+ public static final int SNAP_TO_3_10_45_45 = 7;
/**
* These snap targets are used for split pairs in a stable, non-transient state. They may be
@@ -81,9 +121,14 @@ public class SplitScreenConstants {
* {@link SnapPosition}.
*/
@IntDef(prefix = { "SNAP_TO_" }, value = {
- SNAP_TO_30_70,
- SNAP_TO_50_50,
- SNAP_TO_70_30
+ SNAP_TO_2_33_66,
+ SNAP_TO_2_50_50,
+ SNAP_TO_2_66_33,
+ SNAP_TO_2_90_10,
+ SNAP_TO_2_10_90,
+ SNAP_TO_3_33_33_33,
+ SNAP_TO_3_45_45_10,
+ SNAP_TO_3_10_45_45,
})
public @interface PersistentSnapPosition {}
@@ -91,9 +136,14 @@ public class SplitScreenConstants {
* Checks if the snapPosition in question is a {@link PersistentSnapPosition}.
*/
public static boolean isPersistentSnapPosition(@SnapPosition int snapPosition) {
- return snapPosition == SNAP_TO_30_70
- || snapPosition == SNAP_TO_50_50
- || snapPosition == SNAP_TO_70_30;
+ return snapPosition == SNAP_TO_2_33_66
+ || snapPosition == SNAP_TO_2_50_50
+ || snapPosition == SNAP_TO_2_66_33
+ || snapPosition == SNAP_TO_2_90_10
+ || snapPosition == SNAP_TO_2_10_90
+ || snapPosition == SNAP_TO_3_33_33_33
+ || snapPosition == SNAP_TO_3_45_45_10
+ || snapPosition == SNAP_TO_3_10_45_45;
}
/** The divider doesn't snap to any target and is freely placeable. */
@@ -109,9 +159,14 @@ public class SplitScreenConstants {
public static final int SNAP_TO_MINIMIZE = 13;
@IntDef(prefix = { "SNAP_TO_" }, value = {
- SNAP_TO_30_70,
- SNAP_TO_50_50,
- SNAP_TO_70_30,
+ SNAP_TO_2_33_66,
+ SNAP_TO_2_50_50,
+ SNAP_TO_2_66_33,
+ SNAP_TO_2_90_10,
+ SNAP_TO_2_10_90,
+ SNAP_TO_3_33_33_33,
+ SNAP_TO_3_45_45_10,
+ SNAP_TO_3_10_45_45,
SNAP_TO_NONE,
SNAP_TO_START_AND_DISMISS,
SNAP_TO_END_AND_DISMISS,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
index f7f45ae36eda..9f100facc163 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
@@ -19,9 +19,9 @@ package com.android.wm.shell.common.split;
import static android.view.WindowManager.DOCKED_LEFT;
import static android.view.WindowManager.DOCKED_RIGHT;
-import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_30_70;
-import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_50_50;
-import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_70_30;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_33_66;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_66_33;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_END_AND_DISMISS;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_MINIMIZE;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_NONE;
@@ -283,10 +283,10 @@ public class DividerSnapAlgorithm {
private void addNonDismissingTargets(boolean isHorizontalDivision, int topPosition,
int bottomPosition, int dividerMax) {
- maybeAddTarget(topPosition, topPosition - getStartInset(), SNAP_TO_30_70);
+ maybeAddTarget(topPosition, topPosition - getStartInset(), SNAP_TO_2_33_66);
addMiddleTarget(isHorizontalDivision);
maybeAddTarget(bottomPosition,
- dividerMax - getEndInset() - (bottomPosition + mDividerSize), SNAP_TO_70_30);
+ dividerMax - getEndInset() - (bottomPosition + mDividerSize), SNAP_TO_2_66_33);
}
private void addFixedDivisionTargets(boolean isHorizontalDivision, int dividerMax) {
@@ -332,7 +332,7 @@ public class DividerSnapAlgorithm {
private void addMiddleTarget(boolean isHorizontalDivision) {
int position = DockedDividerUtils.calculateMiddlePosition(isHorizontalDivision,
mInsets, mDisplayWidth, mDisplayHeight, mDividerSize);
- mTargets.add(new SnapTarget(position, SNAP_TO_50_50));
+ mTargets.add(new SnapTarget(position, SNAP_TO_2_50_50));
}
private void addMinimizedTarget(boolean isHorizontalDivision, int dockedSide) {
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 bec2ea58e106..4227a6e2903f 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
@@ -123,6 +123,7 @@ import com.android.wm.shell.sysui.ShellInterface;
import com.android.wm.shell.taskview.TaskViewFactory;
import com.android.wm.shell.taskview.TaskViewFactoryController;
import com.android.wm.shell.taskview.TaskViewTransitions;
+import com.android.wm.shell.transition.FocusTransitionObserver;
import com.android.wm.shell.transition.HomeTransitionObserver;
import com.android.wm.shell.transition.MixedTransitionHandler;
import com.android.wm.shell.transition.Transitions;
@@ -742,14 +743,15 @@ public abstract class WMShellBaseModule {
@ShellMainThread Handler mainHandler,
@ShellAnimationThread ShellExecutor animExecutor,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
- HomeTransitionObserver homeTransitionObserver) {
+ HomeTransitionObserver homeTransitionObserver,
+ FocusTransitionObserver focusTransitionObserver) {
if (!context.getResources().getBoolean(R.bool.config_registerShellTransitionsOnInit)) {
// TODO(b/238217847): Force override shell init if registration is disabled
shellInit = new ShellInit(mainExecutor);
}
return new Transitions(context, shellInit, shellCommandHandler, shellController, organizer,
pool, displayController, mainExecutor, mainHandler, animExecutor,
- rootTaskDisplayAreaOrganizer, homeTransitionObserver);
+ rootTaskDisplayAreaOrganizer, homeTransitionObserver, focusTransitionObserver);
}
@WMSingleton
@@ -761,6 +763,12 @@ public abstract class WMShellBaseModule {
@WMSingleton
@Provides
+ static FocusTransitionObserver provideFocusTransitionObserver() {
+ return new FocusTransitionObserver();
+ }
+
+ @WMSingleton
+ @Provides
static TaskViewTransitions provideTaskViewTransitions(Transitions transitions) {
return new TaskViewTransitions(transitions);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
index 759ed035895e..0e8c4e70e05d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
@@ -456,6 +456,7 @@ class DesktopModeTaskRepository (
pw.println(
"${innerPrefix}freeformTasksInZOrder=${data.freeformTasksInZOrder.toDumpString()}"
)
+ pw.println("${innerPrefix}minimizedTasks=${data.minimizedTasks.toDumpString()}")
}
}
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 968f40c3df5d..afa27f9f1309 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
@@ -43,6 +43,7 @@ import android.view.Display.DEFAULT_DISPLAY
import android.view.DragEvent
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_CHANGE
+import android.view.WindowManager.TRANSIT_CLOSE
import android.view.WindowManager.TRANSIT_NONE
import android.view.WindowManager.TRANSIT_OPEN
import android.view.WindowManager.TRANSIT_TO_FRONT
@@ -1061,7 +1062,10 @@ class DesktopTasksController(
// Check if freeform task launch during recents should be handled
shouldHandleMidRecentsFreeformLaunch -> handleMidRecentsFreeformTaskLaunch(task)
// Check if the closing task needs to be handled
- TransitionUtil.isClosingType(request.type) -> handleTaskClosing(task)
+ TransitionUtil.isClosingType(request.type) -> handleTaskClosing(
+ task,
+ request.type
+ )
// Check if the top task shouldn't be allowed to enter desktop mode
isIncompatibleTask(task) -> handleIncompatibleTaskLaunch(task)
// Check if fullscreen task should be updated
@@ -1288,7 +1292,10 @@ class DesktopTasksController(
}
/** Handle task closing by removing wallpaper activity if it's the last active task */
- private fun handleTaskClosing(task: RunningTaskInfo): WindowContainerTransaction? {
+ private fun handleTaskClosing(
+ task: RunningTaskInfo,
+ transitionType: Int
+ ): WindowContainerTransaction? {
logV("handleTaskClosing")
if (!isDesktopModeShowing(task.displayId))
return null
@@ -1301,9 +1308,10 @@ class DesktopTasksController(
removeWallpaperActivity(wct)
}
taskRepository.addClosingTask(task.displayId, task.taskId)
- // If a CLOSE or TO_BACK is triggered on a desktop task, remove the task.
+ // If a CLOSE is triggered on a desktop task, remove the task.
if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue() &&
- taskRepository.isVisibleTask(task.taskId)
+ taskRepository.isVisibleTask(task.taskId) &&
+ transitionType == TRANSIT_CLOSE
) {
wct.removeTask(task.token)
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
index 0841628853a3..4796c4d0655a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
@@ -16,16 +16,19 @@
package com.android.wm.shell.desktopmode
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.content.Context
import android.os.IBinder
import android.view.SurfaceControl
import android.view.WindowManager
+import android.view.WindowManager.TRANSIT_TO_BACK
import android.window.TransitionInfo
import android.window.WindowContainerTransaction
+import android.window.flags.DesktopModeFlags
+import android.window.flags.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY
import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
-import android.window.flags.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
@@ -64,6 +67,30 @@ class DesktopTasksTransitionObserver(
) {
// TODO: b/332682201 Update repository state
updateWallpaperToken(info)
+
+ if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) {
+ handleBackNavigation(info)
+ }
+ }
+
+ private fun handleBackNavigation(info: TransitionInfo) {
+ // When default back navigation happens, transition type is TO_BACK and the change is
+ // TO_BACK. Mark the task going to back as minimized.
+ if (info.type == TRANSIT_TO_BACK) {
+ for (change in info.changes) {
+ val taskInfo = change.taskInfo
+ if (taskInfo == null || taskInfo.taskId == -1) {
+ continue
+ }
+
+ if (desktopModeTaskRepository.getVisibleTaskCount(taskInfo.displayId) > 0 &&
+ change.mode == TRANSIT_TO_BACK &&
+ taskInfo.windowingMode == WINDOWING_MODE_FREEFORM
+ ) {
+ desktopModeTaskRepository.minimizeTask(taskInfo.displayId, taskInfo.taskId)
+ }
+ }
+ }
}
override fun onTransitionStarting(transition: IBinder) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 2138acc51eb2..cbb08b804dfe 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -1344,6 +1344,9 @@ public class PipTransition extends PipTransitionController {
final SurfaceControl leash = pipChange.getLeash();
final Rect destBounds = mPipOrganizer.getCurrentOrAnimatingBounds();
final boolean isInPip = mPipTransitionState.isInPip();
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Update pip for unhandled transition, change=%s, destBounds=%s, isInPip=%b",
+ TAG, pipChange, destBounds, isInPip);
mSurfaceTransactionHelper
.crop(startTransaction, leash, destBounds)
.round(startTransaction, leash, isInPip)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index af6844262771..7f6118689dad 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -592,8 +592,9 @@ public class PipController implements PipTransitionController.PipTransitionCallb
public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "onActivityRestartAttempt: %s", task.topActivity);
- if (task.getWindowingMode() != WINDOWING_MODE_PINNED) {
+ "onActivityRestartAttempt: topActivity=%s, wasVisible=%b",
+ task.topActivity, wasVisible);
+ if (task.getWindowingMode() != WINDOWING_MODE_PINNED || !wasVisible) {
return;
}
if (mPipTaskOrganizer.isLaunchToSplit(task)) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index d3bed59f7994..a2439a937512 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -361,8 +361,11 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
final int anim = getRotationAnimationHint(change, info, mDisplayController);
isSeamlessDisplayChange = anim == ROTATION_ANIMATION_SEAMLESS;
if (!(isSeamlessDisplayChange || anim == ROTATION_ANIMATION_JUMPCUT)) {
- startRotationAnimation(startTransaction, change, info, anim, animations,
- onAnimFinish);
+ final int flags = wallpaperTransit != WALLPAPER_TRANSITION_NONE
+ && Flags.commonSurfaceAnimator()
+ ? ScreenRotationAnimation.FLAG_HAS_WALLPAPER : 0;
+ startRotationAnimation(startTransaction, change, info, anim, flags,
+ animations, onAnimFinish);
isDisplayRotationAnimationStarted = true;
continue;
}
@@ -414,7 +417,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
if (change.getParent() == null && !change.hasFlags(FLAG_IS_DISPLAY)
&& change.getStartRotation() != change.getEndRotation()) {
startRotationAnimation(startTransaction, change, info,
- ROTATION_ANIMATION_ROTATE, animations, onAnimFinish);
+ ROTATION_ANIMATION_ROTATE, 0 /* flags */, animations, onAnimFinish);
continue;
}
}
@@ -699,12 +702,12 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
}
private void startRotationAnimation(SurfaceControl.Transaction startTransaction,
- TransitionInfo.Change change, TransitionInfo info, int animHint,
+ TransitionInfo.Change change, TransitionInfo info, int animHint, int flags,
ArrayList<Animator> animations, Runnable onAnimFinish) {
final int rootIdx = TransitionUtil.rootIndexFor(change, info);
final ScreenRotationAnimation anim = new ScreenRotationAnimation(mContext,
mTransactionPool, startTransaction, change, info.getRoot(rootIdx).getLeash(),
- animHint);
+ animHint, flags);
// The rotation animation may consist of 3 animations: fade-out screenshot, fade-in real
// content, and background color. The item of "animGroup" will be removed if the sub
// animation is finished. Then if the list becomes empty, the rotation animation is done.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/FocusTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/FocusTransitionObserver.java
new file mode 100644
index 000000000000..2f5059f3161c
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/FocusTransitionObserver.java
@@ -0,0 +1,142 @@
+/*
+ * 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.transition;
+
+import static android.view.Display.INVALID_DISPLAY;
+import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP;
+
+import static com.android.window.flags.Flags.enableDisplayFocusInShellTransitions;
+import static com.android.wm.shell.transition.Transitions.TransitionObserver;
+
+import android.annotation.NonNull;
+import android.app.ActivityManager.RunningTaskInfo;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+import android.view.SurfaceControl;
+import android.window.TransitionInfo;
+
+import com.android.wm.shell.shared.FocusTransitionListener;
+import com.android.wm.shell.shared.IFocusTransitionListener;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+/**
+ * The {@link TransitionObserver} that observes for transitions involving focus switch.
+ * It reports transitions to callers outside of the process via {@link IFocusTransitionListener},
+ * and callers within the process via {@link FocusTransitionListener}.
+ */
+public class FocusTransitionObserver implements TransitionObserver {
+ private static final String TAG = FocusTransitionObserver.class.getSimpleName();
+
+ private IFocusTransitionListener mRemoteListener;
+ private final Map<FocusTransitionListener, Executor> mLocalListeners =
+ new HashMap<>();
+
+ private int mFocusedDisplayId = INVALID_DISPLAY;
+
+ public FocusTransitionObserver() {}
+
+ @Override
+ public void onTransitionReady(@NonNull IBinder transition,
+ @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction) {
+ final List<TransitionInfo.Change> changes = info.getChanges();
+ for (int i = changes.size() - 1; i >= 0; i--) {
+ final TransitionInfo.Change change = changes.get(i);
+ final RunningTaskInfo task = change.getTaskInfo();
+ if (task != null && task.isFocused && change.hasFlags(FLAG_MOVED_TO_TOP)) {
+ if (mFocusedDisplayId != task.displayId) {
+ mFocusedDisplayId = task.displayId;
+ notifyFocusedDisplayChanged();
+ }
+ return;
+ }
+ }
+ }
+
+ @Override
+ public void onTransitionStarting(@NonNull IBinder transition) {}
+
+ @Override
+ public void onTransitionMerged(@NonNull IBinder merged, @NonNull IBinder playing) {}
+
+ @Override
+ public void onTransitionFinished(@NonNull IBinder transition, boolean aborted) {}
+
+ /**
+ * Sets the focus transition listener that receives any transitions resulting in focus switch.
+ * This is for calls from outside the Shell, within the host process.
+ *
+ */
+ public void setLocalFocusTransitionListener(FocusTransitionListener listener,
+ Executor executor) {
+ if (!enableDisplayFocusInShellTransitions()) {
+ return;
+ }
+ mLocalListeners.put(listener, executor);
+ executor.execute(() -> listener.onFocusedDisplayChanged(mFocusedDisplayId));
+ }
+
+ /**
+ * Sets the focus transition listener that receives any transitions resulting in focus switch.
+ * This is for calls from outside the Shell, within the host process.
+ *
+ */
+ public void unsetLocalFocusTransitionListener(FocusTransitionListener listener) {
+ if (!enableDisplayFocusInShellTransitions()) {
+ return;
+ }
+ mLocalListeners.remove(listener);
+ }
+
+ /**
+ * Sets the focus transition listener that receives any transitions resulting in focus switch.
+ * This is for calls from outside the host process.
+ */
+ public void setRemoteFocusTransitionListener(Transitions transitions,
+ IFocusTransitionListener listener) {
+ if (!enableDisplayFocusInShellTransitions()) {
+ return;
+ }
+ mRemoteListener = listener;
+ notifyFocusedDisplayChangedToRemote();
+ }
+
+ /**
+ * Notifies the listener that display focus has changed.
+ */
+ public void notifyFocusedDisplayChanged() {
+ notifyFocusedDisplayChangedToRemote();
+ mLocalListeners.forEach((listener, executor) ->
+ executor.execute(() -> listener.onFocusedDisplayChanged(mFocusedDisplayId)));
+ }
+
+ private void notifyFocusedDisplayChangedToRemote() {
+ if (mRemoteListener != null) {
+ try {
+ mRemoteListener.onFocusedDisplayChanged(mFocusedDisplayId);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed call notifyFocusedDisplayChangedToRemote", e);
+ }
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
index 5802e2ca8133..1a04997fa384 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
@@ -25,12 +25,9 @@ import static com.android.wm.shell.transition.DefaultTransitionHandler.buildSurf
import static com.android.wm.shell.transition.Transitions.TAG;
import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ArgbEvaluator;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.content.Context;
-import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.hardware.HardwareBuffer;
@@ -38,6 +35,7 @@ import android.util.Slog;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
+import android.view.animation.AccelerateInterpolator;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.window.ScreenCapture;
@@ -74,6 +72,7 @@ import java.util.ArrayList;
*/
class ScreenRotationAnimation {
static final int MAX_ANIMATION_DURATION = 10 * 1000;
+ static final int FLAG_HAS_WALLPAPER = 1;
private final Context mContext;
private final TransactionPool mTransactionPool;
@@ -98,6 +97,12 @@ class ScreenRotationAnimation {
private SurfaceControl mBackColorSurface;
/** The leash using to animate screenshot layer. */
private final SurfaceControl mAnimLeash;
+ /**
+ * The container with background color for {@link #mSurfaceControl}. It is only created if
+ * {@link #mSurfaceControl} may be translucent. E.g. visible wallpaper with alpha < 1 (dimmed).
+ * That prevents flickering of alpha blending.
+ */
+ private SurfaceControl mBackEffectSurface;
// The current active animation to move from the old to the new rotated
// state. Which animation is run here will depend on the old and new
@@ -111,8 +116,8 @@ class ScreenRotationAnimation {
/** Intensity of light/whiteness of the layout after rotation occurs. */
private float mEndLuma;
- ScreenRotationAnimation(Context context, TransactionPool pool,
- Transaction t, TransitionInfo.Change change, SurfaceControl rootLeash, int animHint) {
+ ScreenRotationAnimation(Context context, TransactionPool pool, Transaction t,
+ TransitionInfo.Change change, SurfaceControl rootLeash, int animHint, int flags) {
mContext = context;
mTransactionPool = pool;
mAnimHint = animHint;
@@ -170,11 +175,20 @@ class ScreenRotationAnimation {
}
hardwareBuffer.close();
}
+ if ((flags & FLAG_HAS_WALLPAPER) != 0) {
+ mBackEffectSurface = new SurfaceControl.Builder()
+ .setCallsite("ShellRotationAnimation").setParent(rootLeash)
+ .setEffectLayer().setOpaque(true).setName("BackEffect").build();
+ t.reparent(mSurfaceControl, mBackEffectSurface)
+ .setColor(mBackEffectSurface,
+ new float[] {mStartLuma, mStartLuma, mStartLuma})
+ .show(mBackEffectSurface);
+ }
t.setLayer(mAnimLeash, SCREEN_FREEZE_LAYER_BASE);
t.show(mAnimLeash);
// Crop the real content in case it contains a larger child layer, e.g. wallpaper.
- t.setCrop(mSurfaceControl, new Rect(0, 0, mEndWidth, mEndHeight));
+ t.setCrop(getEnterSurface(), new Rect(0, 0, mEndWidth, mEndHeight));
if (!isCustomRotate()) {
mBackColorSurface = new SurfaceControl.Builder()
@@ -202,6 +216,11 @@ class ScreenRotationAnimation {
return mAnimHint == ROTATION_ANIMATION_CROSSFADE || mAnimHint == ROTATION_ANIMATION_JUMPCUT;
}
+ /** Returns the surface which contains the real content to animate enter. */
+ private SurfaceControl getEnterSurface() {
+ return mBackEffectSurface != null ? mBackEffectSurface : mSurfaceControl;
+ }
+
private void setScreenshotTransform(SurfaceControl.Transaction t) {
if (mScreenshotLayer == null) {
return;
@@ -314,7 +333,11 @@ class ScreenRotationAnimation {
} else {
startDisplayRotation(animations, finishCallback, mainExecutor);
startScreenshotRotationAnimation(animations, finishCallback, mainExecutor);
- //startColorAnimation(mTransaction, animationScale);
+ if (mBackEffectSurface != null && mStartLuma > 0.1f) {
+ // Animate from the color of background to black for smooth alpha blending.
+ buildLumaAnimation(animations, mStartLuma, 0f /* endLuma */, mBackEffectSurface,
+ animationScale, finishCallback, mainExecutor);
+ }
}
return true;
@@ -322,7 +345,7 @@ class ScreenRotationAnimation {
private void startDisplayRotation(@NonNull ArrayList<Animator> animations,
@NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor) {
- buildSurfaceAnimation(animations, mRotateEnterAnimation, mSurfaceControl, finishCallback,
+ buildSurfaceAnimation(animations, mRotateEnterAnimation, getEnterSurface(), finishCallback,
mTransactionPool, mainExecutor, null /* position */, 0 /* cornerRadius */,
null /* clipRect */, false /* isActivity */);
}
@@ -341,40 +364,17 @@ class ScreenRotationAnimation {
null /* clipRect */, false /* isActivity */);
}
- private void startColorAnimation(float animationScale, @NonNull ShellExecutor animExecutor) {
- int colorTransitionMs = mContext.getResources().getInteger(
- R.integer.config_screen_rotation_color_transition);
- final float[] rgbTmpFloat = new float[3];
- final int startColor = Color.rgb(mStartLuma, mStartLuma, mStartLuma);
- final int endColor = Color.rgb(mEndLuma, mEndLuma, mEndLuma);
- final long duration = colorTransitionMs * (long) animationScale;
- final Transaction t = mTransactionPool.acquire();
-
- final ValueAnimator va = ValueAnimator.ofFloat(0f, 1f);
- // Animation length is already expected to be scaled.
- va.overrideDurationScale(1.0f);
- va.setDuration(duration);
- va.addUpdateListener(animation -> {
- final long currentPlayTime = Math.min(va.getDuration(), va.getCurrentPlayTime());
- final float fraction = currentPlayTime / va.getDuration();
- applyColor(startColor, endColor, rgbTmpFloat, fraction, mBackColorSurface, t);
- });
- va.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationCancel(Animator animation) {
- applyColor(startColor, endColor, rgbTmpFloat, 1f /* fraction */, mBackColorSurface,
- t);
- mTransactionPool.release(t);
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- applyColor(startColor, endColor, rgbTmpFloat, 1f /* fraction */, mBackColorSurface,
- t);
- mTransactionPool.release(t);
- }
- });
- animExecutor.execute(va::start);
+ private void buildLumaAnimation(@NonNull ArrayList<Animator> animations,
+ float startLuma, float endLuma, SurfaceControl surface, float animationScale,
+ @NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor) {
+ final long durationMillis = (long) (mContext.getResources().getInteger(
+ R.integer.config_screen_rotation_color_transition) * animationScale);
+ final LumaAnimation animation = new LumaAnimation(durationMillis);
+ // Align the end with the enter animation.
+ animation.setStartOffset(mRotateEnterAnimation.getDuration() - durationMillis);
+ final LumaAnimationAdapter adapter = new LumaAnimationAdapter(surface, startLuma, endLuma);
+ DefaultSurfaceAnimator.buildSurfaceAnimation(animations, animation, finishCallback,
+ mTransactionPool, mainExecutor, adapter);
}
public void kill() {
@@ -389,21 +389,47 @@ class ScreenRotationAnimation {
if (mBackColorSurface != null && mBackColorSurface.isValid()) {
t.remove(mBackColorSurface);
}
+ if (mBackEffectSurface != null && mBackEffectSurface.isValid()) {
+ t.remove(mBackEffectSurface);
+ }
t.apply();
mTransactionPool.release(t);
}
- private static void applyColor(int startColor, int endColor, float[] rgbFloat,
- float fraction, SurfaceControl surface, SurfaceControl.Transaction t) {
- final int color = (Integer) ArgbEvaluator.getInstance().evaluate(fraction, startColor,
- endColor);
- Color middleColor = Color.valueOf(color);
- rgbFloat[0] = middleColor.red();
- rgbFloat[1] = middleColor.green();
- rgbFloat[2] = middleColor.blue();
- if (surface.isValid()) {
- t.setColor(surface, rgbFloat);
+ /** A no-op wrapper to provide animation duration. */
+ private static class LumaAnimation extends Animation {
+ LumaAnimation(long durationMillis) {
+ setDuration(durationMillis);
+ }
+ }
+
+ private static class LumaAnimationAdapter extends DefaultSurfaceAnimator.AnimationAdapter {
+ final float[] mColorArray = new float[3];
+ final float mStartLuma;
+ final float mEndLuma;
+ final AccelerateInterpolator mInterpolation;
+
+ LumaAnimationAdapter(@NonNull SurfaceControl leash, float startLuma, float endLuma) {
+ super(leash);
+ mStartLuma = startLuma;
+ mEndLuma = endLuma;
+ // Make the initial progress color lighter if the background is light. That avoids
+ // darker content when fading into the entering surface.
+ final float factor = Math.min(3f, (Math.max(0.5f, mStartLuma) - 0.5f) * 10);
+ Slog.d(TAG, "Luma=" + mStartLuma + " factor=" + factor);
+ mInterpolation = factor > 0.5f ? new AccelerateInterpolator(factor) : null;
+ }
+
+ @Override
+ void applyTransformation(ValueAnimator animator, long currentPlayTime) {
+ final float fraction = mInterpolation != null
+ ? mInterpolation.getInterpolation(animator.getAnimatedFraction())
+ : animator.getAnimatedFraction();
+ final float luma = mStartLuma + fraction * (mEndLuma - mStartLuma);
+ mColorArray[0] = luma;
+ mColorArray[1] = luma;
+ mColorArray[2] = luma;
+ mTransaction.setColor(mLeash, mColorArray);
}
- t.apply();
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index d03832d3e85e..d280dcd252b4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -87,6 +87,8 @@ import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.FocusTransitionListener;
+import com.android.wm.shell.shared.IFocusTransitionListener;
import com.android.wm.shell.shared.IHomeTransitionListener;
import com.android.wm.shell.shared.IShellTransitions;
import com.android.wm.shell.shared.ShellTransitions;
@@ -103,6 +105,7 @@ import com.android.wm.shell.transition.tracing.TransitionTracer;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.concurrent.Executor;
/**
* Plays transition animations. Within this player, each transition has a lifecycle.
@@ -224,6 +227,7 @@ public class Transitions implements RemoteCallable<Transitions>,
private final ArrayList<TransitionObserver> mObservers = new ArrayList<>();
private HomeTransitionObserver mHomeTransitionObserver;
+ private FocusTransitionObserver mFocusTransitionObserver;
/** List of {@link Runnable} instances to run when the last active transition has finished. */
private final ArrayList<Runnable> mRunWhenIdleQueue = new ArrayList<>();
@@ -309,10 +313,12 @@ public class Transitions implements RemoteCallable<Transitions>,
@NonNull ShellExecutor mainExecutor,
@NonNull Handler mainHandler,
@NonNull ShellExecutor animExecutor,
- @NonNull HomeTransitionObserver observer) {
+ @NonNull HomeTransitionObserver homeTransitionObserver,
+ @NonNull FocusTransitionObserver focusTransitionObserver) {
this(context, shellInit, new ShellCommandHandler(), shellController, organizer, pool,
displayController, mainExecutor, mainHandler, animExecutor,
- new RootTaskDisplayAreaOrganizer(mainExecutor, context, shellInit), observer);
+ new RootTaskDisplayAreaOrganizer(mainExecutor, context, shellInit),
+ homeTransitionObserver, focusTransitionObserver);
}
public Transitions(@NonNull Context context,
@@ -326,7 +332,8 @@ public class Transitions implements RemoteCallable<Transitions>,
@NonNull Handler mainHandler,
@NonNull ShellExecutor animExecutor,
@NonNull RootTaskDisplayAreaOrganizer rootTDAOrganizer,
- @NonNull HomeTransitionObserver observer) {
+ @NonNull HomeTransitionObserver homeTransitionObserver,
+ @NonNull FocusTransitionObserver focusTransitionObserver) {
mOrganizer = organizer;
mContext = context;
mMainExecutor = mainExecutor;
@@ -345,7 +352,8 @@ public class Transitions implements RemoteCallable<Transitions>,
mHandlers.add(mRemoteTransitionHandler);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: Remote");
shellInit.addInitCallback(this::onInit, this);
- mHomeTransitionObserver = observer;
+ mHomeTransitionObserver = homeTransitionObserver;
+ mFocusTransitionObserver = focusTransitionObserver;
if (android.tracing.Flags.perfettoTransitionTracing()) {
mTransitionTracer = new PerfettoTransitionTracer();
@@ -384,6 +392,8 @@ public class Transitions implements RemoteCallable<Transitions>,
mShellCommandHandler.addCommandCallback("transitions", this, this);
mShellCommandHandler.addDumpCallback(this::dump, this);
+
+ registerObserver(mFocusTransitionObserver);
}
public boolean isRegistered() {
@@ -1573,6 +1583,21 @@ public class Transitions implements RemoteCallable<Transitions>,
mMainExecutor.execute(
() -> mRemoteTransitionHandler.removeFiltered(remoteTransition));
}
+
+ @Override
+ public void setFocusTransitionListener(FocusTransitionListener listener,
+ Executor executor) {
+ mMainExecutor.execute(() ->
+ mFocusTransitionObserver.setLocalFocusTransitionListener(listener, executor));
+
+ }
+
+ @Override
+ public void unsetFocusTransitionListener(FocusTransitionListener listener) {
+ mMainExecutor.execute(() ->
+ mFocusTransitionObserver.unsetLocalFocusTransitionListener(listener));
+
+ }
}
/**
@@ -1634,6 +1659,15 @@ public class Transitions implements RemoteCallable<Transitions>,
}
@Override
+ public void setFocusTransitionListener(IFocusTransitionListener listener) {
+ executeRemoteCallWithTaskPermission(mTransitions, "setFocusTransitionListener",
+ (transitions) -> {
+ transitions.mFocusTransitionObserver.setRemoteFocusTransitionListener(
+ transitions, listener);
+ });
+ }
+
+ @Override
public SurfaceControl getHomeTaskOverlayContainer() {
SurfaceControl[] result = new SurfaceControl[1];
executeRemoteCallWithTaskPermission(mTransitions, "getHomeTaskOverlayContainer",
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt
index 226b0fb2e1a1..1be26f080ac8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt
@@ -107,4 +107,27 @@ class AdditionalSystemViewContainer(
}
windowManagerWrapper.updateViewLayout(view, lp)
}
+
+ class Factory {
+ fun create(
+ windowManagerWrapper: WindowManagerWrapper,
+ taskId: Int,
+ x: Int,
+ y: Int,
+ width: Int,
+ height: Int,
+ flags: Int,
+ view: View,
+ ): AdditionalSystemViewContainer =
+ AdditionalSystemViewContainer(
+ windowManagerWrapper = windowManagerWrapper,
+ taskId = taskId,
+ x = x,
+ y = y,
+ width = width,
+ height = height,
+ flags = flags,
+ view = view
+ )
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/education/DesktopWindowingEducationTooltipController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/education/DesktopWindowingEducationTooltipController.kt
new file mode 100644
index 000000000000..98413ee96133
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/education/DesktopWindowingEducationTooltipController.kt
@@ -0,0 +1,249 @@
+/*
+ * 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.windowdecor.education
+
+import android.annotation.DimenRes
+import android.annotation.LayoutRes
+import android.content.Context
+import android.content.res.Resources
+import android.graphics.Point
+import android.util.Size
+import android.view.LayoutInflater
+import android.view.MotionEvent
+import android.view.View
+import android.view.View.MeasureSpec.UNSPECIFIED
+import android.view.WindowManager
+import android.widget.ImageView
+import android.widget.LinearLayout
+import android.widget.TextView
+import androidx.dynamicanimation.animation.DynamicAnimation
+import androidx.dynamicanimation.animation.SpringForce
+import com.android.wm.shell.R
+import com.android.wm.shell.shared.animation.PhysicsAnimator
+import com.android.wm.shell.windowdecor.WindowManagerWrapper
+import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer
+
+/**
+ * Controls the lifecycle of an education tooltip, including showing and hiding it. Ensures that
+ * only one tooltip is displayed at a time.
+ */
+class DesktopWindowingEducationTooltipController(
+ private val context: Context,
+ private val additionalSystemViewContainerFactory: AdditionalSystemViewContainer.Factory,
+) {
+ // TODO: b/369384567 - Set tooltip color scheme to match LT/DT of app theme
+ private var tooltipView: View? = null
+ private var animator: PhysicsAnimator<View>? = null
+ private val springConfig by lazy {
+ PhysicsAnimator.SpringConfig(SpringForce.STIFFNESS_MEDIUM, SpringForce.DAMPING_RATIO_LOW_BOUNCY)
+ }
+ private var popupWindow: AdditionalSystemViewContainer? = null
+
+ /**
+ * Shows education tooltip.
+ *
+ * @param tooltipViewConfig features of tooltip.
+ * @param taskId is used in the title of popup window created for the tooltip view.
+ */
+ fun showEducationTooltip(tooltipViewConfig: EducationViewConfig, taskId: Int) {
+ hideEducationTooltip()
+ tooltipView = createEducationTooltipView(tooltipViewConfig, taskId)
+ animator = createAnimator()
+ animateShowTooltipTransition()
+ }
+
+ /** Hide the current education view if visible */
+ private fun hideEducationTooltip() = animateHideTooltipTransition { cleanUp() }
+
+ /** Create education view by inflating layout provided. */
+ private fun createEducationTooltipView(
+ tooltipViewConfig: EducationViewConfig,
+ taskId: Int,
+ ): View {
+ val tooltipView =
+ LayoutInflater.from(context)
+ .inflate(
+ tooltipViewConfig.tooltipViewLayout, /* root= */ null, /* attachToRoot= */ false)
+ .apply {
+ alpha = 0f
+ scaleX = 0f
+ scaleY = 0f
+
+ requireViewById<TextView>(R.id.tooltip_text).apply {
+ text = tooltipViewConfig.tooltipText
+ }
+
+ setOnTouchListener { _, motionEvent ->
+ if (motionEvent.action == MotionEvent.ACTION_OUTSIDE) {
+ hideEducationTooltip()
+ tooltipViewConfig.onDismissAction()
+ true
+ } else {
+ false
+ }
+ }
+ setOnClickListener {
+ hideEducationTooltip()
+ tooltipViewConfig.onEducationClickAction()
+ }
+ }
+
+ val tooltipDimens = tooltipDimens(tooltipView = tooltipView, tooltipViewConfig.arrowDirection)
+ val tooltipViewGlobalCoordinates =
+ tooltipViewGlobalCoordinates(
+ tooltipViewGlobalCoordinates = tooltipViewConfig.tooltipViewGlobalCoordinates,
+ arrowDirection = tooltipViewConfig.arrowDirection,
+ tooltipDimen = tooltipDimens)
+ createTooltipPopupWindow(
+ taskId, tooltipViewGlobalCoordinates, tooltipDimens, tooltipView = tooltipView)
+
+ return tooltipView
+ }
+
+ /** Create animator for education transitions */
+ private fun createAnimator(): PhysicsAnimator<View>? =
+ tooltipView?.let {
+ PhysicsAnimator.getInstance(it).apply { setDefaultSpringConfig(springConfig) }
+ }
+
+ /** Animate show transition for the education view */
+ private fun animateShowTooltipTransition() {
+ animator
+ ?.spring(DynamicAnimation.ALPHA, 1f)
+ ?.spring(DynamicAnimation.SCALE_X, 1f)
+ ?.spring(DynamicAnimation.SCALE_Y, 1f)
+ ?.start()
+ }
+
+ /** Animate hide transition for the education view */
+ private fun animateHideTooltipTransition(endActions: () -> Unit) {
+ animator
+ ?.spring(DynamicAnimation.ALPHA, 0f)
+ ?.spring(DynamicAnimation.SCALE_X, 0f)
+ ?.spring(DynamicAnimation.SCALE_Y, 0f)
+ ?.start()
+ endActions()
+ }
+
+ /** Remove education tooltip and clean up all relative properties */
+ private fun cleanUp() {
+ tooltipView = null
+ animator = null
+ popupWindow?.releaseView()
+ popupWindow = null
+ }
+
+ private fun createTooltipPopupWindow(
+ taskId: Int,
+ tooltipViewGlobalCoordinates: Point,
+ tooltipDimen: Size,
+ tooltipView: View,
+ ) {
+ popupWindow =
+ additionalSystemViewContainerFactory.create(
+ windowManagerWrapper =
+ WindowManagerWrapper(context.getSystemService(WindowManager::class.java)),
+ taskId = taskId,
+ x = tooltipViewGlobalCoordinates.x,
+ y = tooltipViewGlobalCoordinates.y,
+ width = tooltipDimen.width,
+ height = tooltipDimen.height,
+ flags =
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
+ WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
+ view = tooltipView)
+ }
+
+ private fun tooltipViewGlobalCoordinates(
+ tooltipViewGlobalCoordinates: Point,
+ arrowDirection: TooltipArrowDirection,
+ tooltipDimen: Size,
+ ): Point {
+ var tooltipX = tooltipViewGlobalCoordinates.x
+ var tooltipY = tooltipViewGlobalCoordinates.y
+
+ // Current values of [tooltipX]/[tooltipY] are the coordinates of tip of the arrow.
+ // Parameter x and y passed to [AdditionalSystemViewContainer] is the top left position of
+ // the window to be created. Hence we will need to move the coordinates left/up in order
+ // to position the tooltip correctly.
+ if (arrowDirection == TooltipArrowDirection.UP) {
+ // Arrow is placed at horizontal center on top edge of the tooltip. Hence decrement
+ // half of tooltip width from [tooltipX] to horizontally position the tooltip.
+ tooltipX -= tooltipDimen.width / 2
+ } else {
+ // Arrow is placed at vertical center on the left edge of the tooltip. Hence decrement
+ // half of tooltip height from [tooltipY] to vertically position the tooltip.
+ tooltipY -= tooltipDimen.height / 2
+ }
+ return Point(tooltipX, tooltipY)
+ }
+
+ private fun tooltipDimens(tooltipView: View, arrowDirection: TooltipArrowDirection): Size {
+ val tooltipBackground = tooltipView.requireViewById<LinearLayout>(R.id.tooltip_container)
+ val arrowView = tooltipView.requireViewById<ImageView>(R.id.arrow_icon)
+ tooltipBackground.measure(
+ /* widthMeasureSpec= */ UNSPECIFIED, /* heightMeasureSpec= */ UNSPECIFIED)
+ arrowView.measure(/* widthMeasureSpec= */ UNSPECIFIED, /* heightMeasureSpec= */ UNSPECIFIED)
+
+ var desiredWidth =
+ tooltipBackground.measuredWidth +
+ 2 * loadDimensionPixelSize(R.dimen.desktop_windowing_education_tooltip_padding)
+ var desiredHeight =
+ tooltipBackground.measuredHeight +
+ 2 * loadDimensionPixelSize(R.dimen.desktop_windowing_education_tooltip_padding)
+ if (arrowDirection == TooltipArrowDirection.UP) {
+ // desiredHeight currently does not account for the height of arrow, hence adding it.
+ desiredHeight += arrowView.height
+ } else {
+ // desiredWidth currently does not account for the width of arrow, hence adding it.
+ desiredWidth += arrowView.width
+ }
+
+ return Size(desiredWidth, desiredHeight)
+ }
+
+ private fun loadDimensionPixelSize(@DimenRes resourceId: Int): Int {
+ if (resourceId == Resources.ID_NULL) return 0
+ return context.resources.getDimensionPixelSize(resourceId)
+ }
+
+ /**
+ * The configuration for education view features:
+ *
+ * @property tooltipViewLayout Layout resource ID of the view to be used for education tooltip.
+ * @property tooltipViewGlobalCoordinates Global (screen) coordinates of the tip of the tooltip
+ * arrow.
+ * @property tooltipText Text to be added to the TextView of tooltip.
+ * @property arrowDirection Direction of arrow of the tooltip.
+ * @property onEducationClickAction Lambda to be executed when the tooltip is clicked.
+ * @property onDismissAction Lambda to be executed when the tooltip is dismissed.
+ */
+ data class EducationViewConfig(
+ @LayoutRes val tooltipViewLayout: Int,
+ val tooltipViewGlobalCoordinates: Point,
+ val tooltipText: String,
+ val arrowDirection: TooltipArrowDirection,
+ val onEducationClickAction: () -> Unit,
+ val onDismissAction: () -> Unit,
+ )
+
+ /** Direction of arrow of the tooltip */
+ enum class TooltipArrowDirection {
+ UP,
+ LEFT,
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/AndroidTestTemplate.xml
index 40dbbac32c7f..c8df15d81345 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/AndroidTestTemplate.xml
@@ -24,6 +24,10 @@
<option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
+ <!-- Turns off Wi-fi -->
+ <option name="wifi" value="off"/>
+ <!-- Turns off Bluetooth -->
+ <option name="bluetooth" value="off"/>
<!-- prevents the phone from restarting -->
<option name="force-skip-system-props" value="true"/>
<!-- set WM tracing verbose level to all -->
diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/AndroidTestTemplate.xml
index 85715db3d952..706c63244890 100644
--- a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/AndroidTestTemplate.xml
@@ -24,6 +24,10 @@
<option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
+ <!-- Turns off Wi-fi -->
+ <option name="wifi" value="off"/>
+ <!-- Turns off Bluetooth -->
+ <option name="bluetooth" value="off"/>
<!-- prevents the phone from restarting -->
<option name="force-skip-system-props" value="true"/>
<!-- set WM tracing verbose level to all -->
diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/AndroidTestTemplate.xml
index 6c903a2e8c42..7df1675f541c 100644
--- a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/AndroidTestTemplate.xml
@@ -24,6 +24,10 @@
<option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
+ <!-- Turns off Wi-fi -->
+ <option name="wifi" value="off"/>
+ <!-- Turns off Bluetooth -->
+ <option name="bluetooth" value="off"/>
<!-- prevents the phone from restarting -->
<option name="force-skip-system-props" value="true"/>
<!-- set WM tracing verbose level to all -->
diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/AndroidTestTemplate.xml
index 6c903a2e8c42..7df1675f541c 100644
--- a/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/AndroidTestTemplate.xml
@@ -24,6 +24,10 @@
<option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
+ <!-- Turns off Wi-fi -->
+ <option name="wifi" value="off"/>
+ <!-- Turns off Bluetooth -->
+ <option name="bluetooth" value="off"/>
<!-- prevents the phone from restarting -->
<option name="force-skip-system-props" value="true"/>
<!-- set WM tracing verbose level to all -->
diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml
index f69a90cc793f..d87c1795cf7b 100644
--- a/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml
@@ -24,6 +24,10 @@
<option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
+ <!-- Turns off Wi-fi -->
+ <option name="wifi" value="off"/>
+ <!-- Turns off Bluetooth -->
+ <option name="bluetooth" value="off"/>
<!-- prevents the phone from restarting -->
<option name="force-skip-system-props" value="true"/>
<!-- set WM tracing verbose level to all -->
diff --git a/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml
index b76d06565700..99969e71238a 100644
--- a/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml
@@ -24,6 +24,10 @@
<option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
+ <!-- Turns off Wi-fi -->
+ <option name="wifi" value="off"/>
+ <!-- Turns off Bluetooth -->
+ <option name="bluetooth" value="off"/>
<!-- prevents the phone from restarting -->
<option name="force-skip-system-props" value="true"/>
<!-- set WM tracing verbose level to all -->
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml
index 041978c371ff..19c3e4048d69 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml
@@ -24,6 +24,10 @@
<option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
+ <!-- Turns off Wi-fi -->
+ <option name="wifi" value="off"/>
+ <!-- Turns off Bluetooth -->
+ <option name="bluetooth" value="off"/>
<!-- prevents the phone from restarting -->
<option name="force-skip-system-props" value="true"/>
<!-- set WM tracing verbose level to all -->
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml b/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml
index bf040d2a95f4..7505860709e9 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml
@@ -24,6 +24,10 @@
<option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
+ <!-- Turns off Wi-fi -->
+ <option name="wifi" value="on"/>
+ <!-- Turns off Bluetooth -->
+ <option name="bluetooth" value="on"/>
<!-- prevents the phone from restarting -->
<option name="force-skip-system-props" value="true"/>
<!-- set WM tracing verbose level to all -->
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
index 177e47a342f6..c52d9dd24165 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
@@ -19,7 +19,7 @@ package com.android.wm.shell.common.split;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
-import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_50_50;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50;
import static com.google.common.truth.Truth.assertThat;
@@ -136,7 +136,7 @@ public class SplitLayoutTests extends ShellTestCase {
@Test
public void testSetDivideRatio() {
mSplitLayout.setDividerPosition(200, false /* applyLayoutChange */);
- mSplitLayout.setDivideRatio(SNAP_TO_50_50);
+ mSplitLayout.setDivideRatio(SNAP_TO_2_50_50);
assertThat(mSplitLayout.getDividerPosition()).isEqualTo(
mSplitLayout.mDividerSnapAlgorithm.getMiddleTarget().position);
}
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 ee545209904f..94e361659090 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
@@ -40,6 +40,7 @@ import android.graphics.Point
import android.graphics.PointF
import android.graphics.Rect
import android.os.Binder
+import android.os.Bundle
import android.os.Handler
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
@@ -2086,16 +2087,13 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
- @EnableFlags(
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
- )
- fun handleRequest_backTransition_singleTaskNoToken_withWallpaper_withBackNav_removesTask() {
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,)
+ fun handleRequest_backTransition_singleTaskNoToken_withWallpaper_removesTask() {
val task = setUpFreeformTask()
val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))
- assertNotNull(result, "Should handle request").assertRemoveAt(0, task.token)
+ assertNull(result, "Should not handle request")
}
@Test
@@ -2137,26 +2135,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
- @EnableFlags(
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
- )
- fun handleRequest_backTransition_singleTask_withWallpaper_withBackNav_removesWallpaperAndTask() {
- val task = setUpFreeformTask()
- val wallpaperToken = MockToken().token()
-
- taskRepository.wallpaperActivityToken = wallpaperToken
- val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))
-
- // Should create remove wallpaper transaction
- assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
- result.assertRemoveAt(index = 1, task.token)
- }
-
- @Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
- fun handleRequest_backTransition_singleTaskWithToken_noBackNav_removesWallpaper() {
+ fun handleRequest_backTransition_singleTaskWithToken_removesWallpaper() {
val task = setUpFreeformTask()
val wallpaperToken = MockToken().token()
@@ -2183,23 +2163,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
- @EnableFlags(
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
- )
- fun handleRequest_backTransition_multipleTasks_withWallpaper_withBackNav_removesTask() {
- val task1 = setUpFreeformTask()
- setUpFreeformTask()
-
- taskRepository.wallpaperActivityToken = MockToken().token()
- val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK))
-
- assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, task1.token)
- }
-
- @Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
fun handleRequest_backTransition_multipleTasks_noBackNav_doesNotHandle() {
val task1 = setUpFreeformTask()
setUpFreeformTask()
@@ -2226,29 +2190,11 @@ class DesktopTasksControllerTest : ShellTestCase() {
// Should create remove wallpaper transaction
assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
- result.assertRemoveAt(index = 1, task1.token)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
- fun handleRequest_backTransition_multipleTasksSingleNonClosing_noBackNav_removesWallpaper() {
- val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
- val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
- val wallpaperToken = MockToken().token()
-
- taskRepository.wallpaperActivityToken = wallpaperToken
- taskRepository.addClosingTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
- val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK))
-
- // Should create remove wallpaper transaction
- assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
}
@Test
@EnableFlags(
Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
- Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION
)
fun handleRequest_backTransition_multipleTasksSingleNonMinimized_removesWallpaperAndTask() {
val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
@@ -2261,23 +2207,6 @@ class DesktopTasksControllerTest : ShellTestCase() {
// Should create remove wallpaper transaction
assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
- result.assertRemoveAt(index = 1, task1.token)
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
- fun handleRequest_backTransition_multipleTasksSingleNonMinimized_noBackNav_removesWallpaper() {
- val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
- val task2 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
- val wallpaperToken = MockToken().token()
-
- taskRepository.wallpaperActivityToken = wallpaperToken
- taskRepository.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = task2.taskId)
- val result = controller.handleRequest(Binder(), createTransition(task1, type = TRANSIT_TO_BACK))
-
- // Should create remove wallpaper transaction
- assertNotNull(result, "Should handle request").assertRemoveAt(index = 0, wallpaperToken)
}
@Test
@@ -2937,6 +2866,108 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES)
+ fun newWindow_fromFullscreenOpensInSplit() {
+ setUpLandscapeDisplay()
+ val task = setUpFullscreenTask()
+ val optionsCaptor = ArgumentCaptor.forClass(Bundle::class.java)
+ runOpenNewWindow(task)
+ verify(splitScreenController)
+ .startIntent(any(), anyInt(), any(), any(),
+ optionsCaptor.capture(), anyOrNull())
+ assertThat(ActivityOptions.fromBundle(optionsCaptor.value).launchWindowingMode)
+ .isEqualTo(WINDOWING_MODE_MULTI_WINDOW)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES)
+ fun newWindow_fromSplitOpensInSplit() {
+ setUpLandscapeDisplay()
+ val task = setUpSplitScreenTask()
+ val optionsCaptor = ArgumentCaptor.forClass(Bundle::class.java)
+ runOpenNewWindow(task)
+ verify(splitScreenController)
+ .startIntent(
+ any(), anyInt(), any(), any(),
+ optionsCaptor.capture(), anyOrNull()
+ )
+ assertThat(ActivityOptions.fromBundle(optionsCaptor.value).launchWindowingMode)
+ .isEqualTo(WINDOWING_MODE_MULTI_WINDOW)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES)
+ fun newWindow_fromFreeformAddsNewWindow() {
+ setUpLandscapeDisplay()
+ val task = setUpFreeformTask()
+ val wctCaptor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ runOpenNewWindow(task)
+ verify(transitions).startTransition(anyInt(), wctCaptor.capture(), anyOrNull())
+ assertThat(ActivityOptions.fromBundle(wctCaptor.value.hierarchyOps[0].launchOptions)
+ .launchWindowingMode).isEqualTo(WINDOWING_MODE_FREEFORM)
+ }
+
+ private fun runOpenNewWindow(task: RunningTaskInfo) {
+ markTaskVisible(task)
+ task.baseActivity = mock(ComponentName::class.java)
+ task.isFocused = true
+ runningTasks.add(task)
+ controller.openNewWindow(task)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES)
+ fun openInstance_fromFullscreenOpensInSplit() {
+ setUpLandscapeDisplay()
+ val task = setUpFullscreenTask()
+ val taskToRequest = setUpFreeformTask()
+ val optionsCaptor = ArgumentCaptor.forClass(Bundle::class.java)
+ runOpenInstance(task, taskToRequest.taskId)
+ verify(splitScreenController)
+ .startTask(anyInt(), anyInt(), optionsCaptor.capture(), anyOrNull())
+ assertThat(ActivityOptions.fromBundle(optionsCaptor.value).launchWindowingMode)
+ .isEqualTo(WINDOWING_MODE_MULTI_WINDOW)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES)
+ fun openInstance_fromSplitOpensInSplit() {
+ setUpLandscapeDisplay()
+ val task = setUpSplitScreenTask()
+ val taskToRequest = setUpFreeformTask()
+ val optionsCaptor = ArgumentCaptor.forClass(Bundle::class.java)
+ runOpenInstance(task, taskToRequest.taskId)
+ verify(splitScreenController)
+ .startTask(anyInt(), anyInt(), optionsCaptor.capture(), anyOrNull())
+ assertThat(ActivityOptions.fromBundle(optionsCaptor.value).launchWindowingMode)
+ .isEqualTo(WINDOWING_MODE_MULTI_WINDOW)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES)
+ fun openInstance_fromFreeformAddsNewWindow() {
+ setUpLandscapeDisplay()
+ val task = setUpFreeformTask()
+ val taskToRequest = setUpFreeformTask()
+ val wctCaptor = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+ runOpenInstance(task, taskToRequest.taskId)
+ verify(transitions).startTransition(anyInt(), wctCaptor.capture(), anyOrNull())
+ assertThat(ActivityOptions.fromBundle(wctCaptor.value.hierarchyOps[0].launchOptions)
+ .launchWindowingMode).isEqualTo(WINDOWING_MODE_FREEFORM)
+ }
+
+ private fun runOpenInstance(
+ callingTask: RunningTaskInfo,
+ requestedTaskId: Int
+ ) {
+ markTaskVisible(callingTask)
+ callingTask.baseActivity = mock(ComponentName::class.java)
+ callingTask.isFocused = true
+ runningTasks.add(callingTask)
+ controller.openInstance(callingTask, requestedTaskId)
+ }
+
+ @Test
fun toggleBounds_togglesToStableBounds() {
val bounds = Rect(0, 0, 100, 100)
val task = setUpFreeformTask(DEFAULT_DISPLAY, bounds)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
new file mode 100644
index 000000000000..c989d1640f80
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.platform.test.annotations.EnableFlags
+import android.view.Display.DEFAULT_DISPLAY
+import android.view.WindowManager.TRANSIT_TO_BACK
+import android.window.IWindowContainerToken
+import android.window.TransitionInfo
+import android.window.TransitionInfo.Change
+import android.window.WindowContainerToken
+import com.android.modules.utils.testing.ExtendedMockitoRule
+import com.android.window.flags.Flags
+import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
+import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.transition.Transitions
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.Mockito
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+class DesktopTasksTransitionObserverTest {
+
+ @JvmField
+ @Rule
+ val extendedMockitoRule =
+ ExtendedMockitoRule.Builder(this)
+ .mockStatic(DesktopModeStatus::class.java)
+ .build()!!
+
+ private val testExecutor = mock<ShellExecutor>()
+ private val mockShellInit = mock<ShellInit>()
+ private val transitions = mock<Transitions>()
+ private val context = mock<Context>()
+ private val shellTaskOrganizer = mock<ShellTaskOrganizer>()
+ private val taskRepository = mock<DesktopModeTaskRepository>()
+
+ private lateinit var transitionObserver: DesktopTasksTransitionObserver
+ private lateinit var shellInit: ShellInit
+
+ @Before
+ fun setup() {
+ whenever(DesktopModeStatus.canEnterDesktopMode(any())).thenReturn(true)
+ shellInit = spy(ShellInit(testExecutor))
+
+ transitionObserver =
+ DesktopTasksTransitionObserver(
+ context, taskRepository, transitions, shellTaskOrganizer, shellInit
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun backNavigation_taskMinimized() {
+ val task = createTaskInfo(1)
+ whenever(taskRepository.getVisibleTaskCount(any())).thenReturn(1)
+
+ transitionObserver.onTransitionReady(
+ transition = mock(),
+ info =
+ createBackNavigationTransition(task),
+ startTransaction = mock(),
+ finishTransaction = mock(),
+ )
+
+ verify(taskRepository).minimizeTask(task.displayId, task.taskId)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
+ fun backNavigation_nullTaskInfo_taskNotMinimized() {
+ val task = createTaskInfo(1)
+ whenever(taskRepository.getVisibleTaskCount(any())).thenReturn(1)
+
+ transitionObserver.onTransitionReady(
+ transition = mock(),
+ info =
+ createBackNavigationTransition(null),
+ startTransaction = mock(),
+ finishTransaction = mock(),
+ )
+
+ verify(taskRepository, never()).minimizeTask(task.displayId, task.taskId)
+ }
+
+ private fun createBackNavigationTransition(
+ task: RunningTaskInfo?
+ ): TransitionInfo {
+ return TransitionInfo(TRANSIT_TO_BACK, 0 /* flags */).apply {
+ addChange(
+ Change(mock(), mock()).apply {
+ mode = TRANSIT_TO_BACK
+ parent = null
+ taskInfo = task
+ flags = flags
+ }
+ )
+ }
+ }
+
+ private fun createTaskInfo(id: Int) =
+ RunningTaskInfo().apply {
+ taskId = id
+ displayId = DEFAULT_DISPLAY
+ configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
+ token = WindowContainerToken(Mockito.mock(IWindowContainerToken::class.java))
+ baseIntent = Intent().apply {
+ component = ComponentName("package", "component.name")
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt
index 0c3f98a324cd..0c100fca2036 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt
@@ -30,7 +30,7 @@ import com.android.wm.shell.shared.GroupedRecentTaskInfo.TYPE_FREEFORM
import com.android.wm.shell.shared.GroupedRecentTaskInfo.TYPE_SINGLE
import com.android.wm.shell.shared.GroupedRecentTaskInfo.TYPE_SPLIT
import com.android.wm.shell.shared.split.SplitBounds
-import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_50_50
+import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50
import com.google.common.truth.Correspondence
import com.google.common.truth.Truth.assertThat
import org.junit.Assert.assertThrows
@@ -136,7 +136,7 @@ class GroupedRecentTaskInfoTest : ShellTestCase() {
assertThat(recentTaskInfoParcel.taskInfo2).isNotNull()
assertThat(recentTaskInfoParcel.taskInfo2!!.taskId).isEqualTo(2)
assertThat(recentTaskInfoParcel.splitBounds).isNotNull()
- assertThat(recentTaskInfoParcel.splitBounds!!.snapPosition).isEqualTo(SNAP_TO_50_50)
+ assertThat(recentTaskInfoParcel.splitBounds!!.snapPosition).isEqualTo(SNAP_TO_2_50_50)
}
@Test
@@ -185,7 +185,7 @@ class GroupedRecentTaskInfoTest : ShellTestCase() {
private fun splitTasksGroupInfo(): GroupedRecentTaskInfo {
val task1 = createTaskInfo(id = 1)
val task2 = createTaskInfo(id = 2)
- val splitBounds = SplitBounds(Rect(), Rect(), 1, 2, SNAP_TO_50_50)
+ val splitBounds = SplitBounds(Rect(), Rect(), 1, 2, SNAP_TO_2_50_50)
return GroupedRecentTaskInfo.forSplitTasks(task1, task2, splitBounds)
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index 386253c19c82..753d4cd153ee 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -22,7 +22,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
-import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_50_50;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -211,10 +211,10 @@ public class RecentTasksControllerTest extends ShellTestCase {
// Verify only one update if the split info is the same
SplitBounds bounds1 = new SplitBounds(new Rect(0, 0, 50, 50),
- new Rect(50, 50, 100, 100), t1.taskId, t2.taskId, SNAP_TO_50_50);
+ new Rect(50, 50, 100, 100), t1.taskId, t2.taskId, SNAP_TO_2_50_50);
mRecentTasksController.addSplitPair(t1.taskId, t2.taskId, bounds1);
SplitBounds bounds2 = new SplitBounds(new Rect(0, 0, 50, 50),
- new Rect(50, 50, 100, 100), t1.taskId, t2.taskId, SNAP_TO_50_50);
+ new Rect(50, 50, 100, 100), t1.taskId, t2.taskId, SNAP_TO_2_50_50);
mRecentTasksController.addSplitPair(t1.taskId, t2.taskId, bounds2);
verify(mRecentTasksController, times(1)).notifyRecentTasksChanged();
}
@@ -246,9 +246,9 @@ public class RecentTasksControllerTest extends ShellTestCase {
// Mark a couple pairs [t2, t4], [t3, t5]
SplitBounds pair1Bounds =
- new SplitBounds(new Rect(), new Rect(), 2, 4, SNAP_TO_50_50);
+ new SplitBounds(new Rect(), new Rect(), 2, 4, SNAP_TO_2_50_50);
SplitBounds pair2Bounds =
- new SplitBounds(new Rect(), new Rect(), 3, 5, SNAP_TO_50_50);
+ new SplitBounds(new Rect(), new Rect(), 3, 5, SNAP_TO_2_50_50);
mRecentTasksController.addSplitPair(t2.taskId, t4.taskId, pair1Bounds);
mRecentTasksController.addSplitPair(t3.taskId, t5.taskId, pair2Bounds);
@@ -277,9 +277,9 @@ public class RecentTasksControllerTest extends ShellTestCase {
// Mark a couple pairs [t2, t4], [t3, t5]
SplitBounds pair1Bounds =
- new SplitBounds(new Rect(), new Rect(), 2, 4, SNAP_TO_50_50);
+ new SplitBounds(new Rect(), new Rect(), 2, 4, SNAP_TO_2_50_50);
SplitBounds pair2Bounds =
- new SplitBounds(new Rect(), new Rect(), 3, 5, SNAP_TO_50_50);
+ new SplitBounds(new Rect(), new Rect(), 3, 5, SNAP_TO_2_50_50);
mRecentTasksController.addSplitPair(t2.taskId, t4.taskId, pair1Bounds);
mRecentTasksController.addSplitPair(t3.taskId, t5.taskId, pair2Bounds);
@@ -339,7 +339,7 @@ public class RecentTasksControllerTest extends ShellTestCase {
setRawList(t1, t2, t3, t4, t5);
SplitBounds pair1Bounds =
- new SplitBounds(new Rect(), new Rect(), 1, 2, SNAP_TO_50_50);
+ new SplitBounds(new Rect(), new Rect(), 1, 2, SNAP_TO_2_50_50);
mRecentTasksController.addSplitPair(t1.taskId, t2.taskId, pair1Bounds);
when(mDesktopModeTaskRepository.isActiveTask(3)).thenReturn(true);
@@ -449,7 +449,7 @@ public class RecentTasksControllerTest extends ShellTestCase {
// Add a pair
SplitBounds pair1Bounds =
- new SplitBounds(new Rect(), new Rect(), 2, 3, SNAP_TO_50_50);
+ new SplitBounds(new Rect(), new Rect(), 2, 3, SNAP_TO_2_50_50);
mRecentTasksController.addSplitPair(t2.taskId, t3.taskId, pair1Bounds);
reset(mRecentTasksController);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/SplitBoundsTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/SplitBoundsTest.java
index 248393cef9ae..be8e6dc3154b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/SplitBoundsTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/SplitBoundsTest.java
@@ -1,6 +1,6 @@
package com.android.wm.shell.recents;
-import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_50_50;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -46,21 +46,21 @@ public class SplitBoundsTest extends ShellTestCase {
@Test
public void testVerticalStacked() {
SplitBounds ssb = new SplitBounds(mTopRect, mBottomRect,
- TASK_ID_1, TASK_ID_2, SNAP_TO_50_50);
+ TASK_ID_1, TASK_ID_2, SNAP_TO_2_50_50);
assertTrue(ssb.appsStackedVertically);
}
@Test
public void testHorizontalStacked() {
SplitBounds ssb = new SplitBounds(mLeftRect, mRightRect,
- TASK_ID_1, TASK_ID_2, SNAP_TO_50_50);
+ TASK_ID_1, TASK_ID_2, SNAP_TO_2_50_50);
assertFalse(ssb.appsStackedVertically);
}
@Test
public void testHorizontalDividerBounds() {
SplitBounds ssb = new SplitBounds(mTopRect, mBottomRect,
- TASK_ID_1, TASK_ID_2, SNAP_TO_50_50);
+ TASK_ID_1, TASK_ID_2, SNAP_TO_2_50_50);
Rect dividerBounds = ssb.visualDividerBounds;
assertEquals(0, dividerBounds.left);
assertEquals(DEVICE_LENGTH / 2 - DIVIDER_SIZE / 2, dividerBounds.top);
@@ -71,7 +71,7 @@ public class SplitBoundsTest extends ShellTestCase {
@Test
public void testVerticalDividerBounds() {
SplitBounds ssb = new SplitBounds(mLeftRect, mRightRect,
- TASK_ID_1, TASK_ID_2, SNAP_TO_50_50);
+ TASK_ID_1, TASK_ID_2, SNAP_TO_2_50_50);
Rect dividerBounds = ssb.visualDividerBounds;
assertEquals(DEVICE_WIDTH / 2 - DIVIDER_SIZE / 2, dividerBounds.left);
assertEquals(0, dividerBounds.top);
@@ -82,7 +82,7 @@ public class SplitBoundsTest extends ShellTestCase {
@Test
public void testEqualVerticalTaskPercent() {
SplitBounds ssb = new SplitBounds(mTopRect, mBottomRect,
- TASK_ID_1, TASK_ID_2, SNAP_TO_50_50);
+ TASK_ID_1, TASK_ID_2, SNAP_TO_2_50_50);
float topPercentSpaceTaken = (float) (DEVICE_LENGTH / 2 - DIVIDER_SIZE / 2) / DEVICE_LENGTH;
assertEquals(topPercentSpaceTaken, ssb.topTaskPercent, 0.01);
}
@@ -90,7 +90,7 @@ public class SplitBoundsTest extends ShellTestCase {
@Test
public void testEqualHorizontalTaskPercent() {
SplitBounds ssb = new SplitBounds(mLeftRect, mRightRect,
- TASK_ID_1, TASK_ID_2, SNAP_TO_50_50);
+ TASK_ID_1, TASK_ID_2, SNAP_TO_2_50_50);
float leftPercentSpaceTaken = (float) (DEVICE_WIDTH / 2 - DIVIDER_SIZE / 2) / DEVICE_WIDTH;
assertEquals(leftPercentSpaceTaken, ssb.leftTaskPercent, 0.01);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/split/SplitScreenConstantsTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/split/SplitScreenConstantsTest.kt
index 19c18be44ab1..ac9606350ebd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/split/SplitScreenConstantsTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/split/SplitScreenConstantsTest.kt
@@ -42,19 +42,44 @@ class SplitScreenConstantsTest {
SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT,
)
assertEquals(
- "the value of SNAP_TO_30_70 should be 0",
+ "the value of SNAP_TO_2_33_66 should be 0",
0,
- SplitScreenConstants.SNAP_TO_30_70,
+ SplitScreenConstants.SNAP_TO_2_33_66,
)
assertEquals(
- "the value of SNAP_TO_50_50 should be 1",
+ "the value of SNAP_TO_2_50_50 should be 1",
1,
- SplitScreenConstants.SNAP_TO_50_50,
+ SplitScreenConstants.SNAP_TO_2_50_50,
)
assertEquals(
- "the value of SNAP_TO_70_30 should be 2",
+ "the value of SNAP_TO_2_66_33 should be 2",
2,
- SplitScreenConstants.SNAP_TO_70_30,
+ SplitScreenConstants.SNAP_TO_2_66_33,
+ )
+ assertEquals(
+ "the value of SNAP_TO_2_90_10 should be 3",
+ 3,
+ SplitScreenConstants.SNAP_TO_2_90_10,
+ )
+ assertEquals(
+ "the value of SNAP_TO_2_10_90 should be 4",
+ 4,
+ SplitScreenConstants.SNAP_TO_2_10_90,
+ )
+ assertEquals(
+ "the value of SNAP_TO_3_33_33_33 should be 5",
+ 5,
+ SplitScreenConstants.SNAP_TO_3_33_33_33,
+ )
+ assertEquals(
+ "the value of SNAP_TO_3_45_45_10 should be 6",
+ 6,
+ SplitScreenConstants.SNAP_TO_3_45_45_10,
+ )
+ assertEquals(
+ "the value of SNAP_TO_3_10_45_45 should be 7",
+ 7,
+ SplitScreenConstants.SNAP_TO_3_10_45_45,
)
}
} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index a6c16c43c8cb..67eda8bfecd1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -74,6 +74,7 @@ import com.android.wm.shell.splitscreen.SplitScreen.SplitScreenListener;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.DefaultMixedHandler;
+import com.android.wm.shell.transition.FocusTransitionObserver;
import com.android.wm.shell.transition.HomeTransitionObserver;
import com.android.wm.shell.transition.Transitions;
@@ -429,7 +430,8 @@ public class StageCoordinatorTests extends ShellTestCase {
ShellInit shellInit = new ShellInit(mMainExecutor);
final Transitions t = new Transitions(mContext, shellInit, mock(ShellController.class),
mTaskOrganizer, mTransactionPool, mock(DisplayController.class), mMainExecutor,
- mMainHandler, mAnimExecutor, mock(HomeTransitionObserver.class));
+ mMainHandler, mAnimExecutor, mock(HomeTransitionObserver.class),
+ mock(FocusTransitionObserver.class));
shellInit.init();
return t;
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/FocusTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/FocusTransitionObserverTest.java
new file mode 100644
index 000000000000..d37b4cf4b4b3
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/FocusTransitionObserverTest.java
@@ -0,0 +1,155 @@
+/*
+ * 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.transition;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager.RunningTaskInfo;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.view.SurfaceControl;
+import android.window.TransitionInfo;
+import android.window.TransitionInfo.TransitionMode;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.window.flags.Flags;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.shared.IFocusTransitionListener;
+import com.android.wm.shell.shared.TransactionPool;
+import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests for the focus transition observer.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+@RequiresFlagsEnabled(Flags.FLAG_ENABLE_DISPLAY_FOCUS_IN_SHELL_TRANSITIONS)
+public class FocusTransitionObserverTest extends ShellTestCase {
+
+ static final int SECONDARY_DISPLAY_ID = 1;
+
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+ private IFocusTransitionListener mListener;
+ private Transitions mTransition;
+ private FocusTransitionObserver mFocusTransitionObserver;
+
+ @Before
+ public void setUp() {
+ mListener = mock(IFocusTransitionListener.class);
+ when(mListener.asBinder()).thenReturn(mock(IBinder.class));
+
+ mFocusTransitionObserver = new FocusTransitionObserver();
+ mTransition =
+ new Transitions(InstrumentationRegistry.getInstrumentation().getTargetContext(),
+ mock(ShellInit.class), mock(ShellController.class),
+ mock(ShellTaskOrganizer.class), mock(TransactionPool.class),
+ mock(DisplayController.class), new TestShellExecutor(),
+ new Handler(Looper.getMainLooper()), new TestShellExecutor(),
+ mock(HomeTransitionObserver.class),
+ mFocusTransitionObserver);
+ mFocusTransitionObserver.setRemoteFocusTransitionListener(mTransition, mListener);
+ }
+
+ @Test
+ public void testTransitionWithMovedToFrontFlagChangesDisplayFocus() throws RemoteException {
+ final IBinder binder = mock(IBinder.class);
+ final SurfaceControl.Transaction tx = mock(SurfaceControl.Transaction.class);
+
+ // Open a task on the default display, which doesn't change display focus because the
+ // default display already has it.
+ TransitionInfo info = mock(TransitionInfo.class);
+ final List<TransitionInfo.Change> changes = new ArrayList<>();
+ setupChange(changes, 123 /* taskId */, TRANSIT_OPEN, DEFAULT_DISPLAY,
+ true /* focused */);
+ when(info.getChanges()).thenReturn(changes);
+ mFocusTransitionObserver.onTransitionReady(binder, info, tx, tx);
+ verify(mListener, never()).onFocusedDisplayChanged(SECONDARY_DISPLAY_ID);
+ clearInvocations(mListener);
+
+ // Open a new task on the secondary display and verify display focus changes to the display.
+ changes.clear();
+ setupChange(changes, 456 /* taskId */, TRANSIT_OPEN, SECONDARY_DISPLAY_ID,
+ true /* focused */);
+ when(info.getChanges()).thenReturn(changes);
+ mFocusTransitionObserver.onTransitionReady(binder, info, tx, tx);
+ verify(mListener, times(1)).onFocusedDisplayChanged(SECONDARY_DISPLAY_ID);
+ clearInvocations(mListener);
+
+ // Open the first task to front and verify display focus goes back to the default display.
+ changes.clear();
+ setupChange(changes, 123 /* taskId */, TRANSIT_TO_FRONT, DEFAULT_DISPLAY,
+ true /* focused */);
+ when(info.getChanges()).thenReturn(changes);
+ mFocusTransitionObserver.onTransitionReady(binder, info, tx, tx);
+ verify(mListener, times(1)).onFocusedDisplayChanged(DEFAULT_DISPLAY);
+ clearInvocations(mListener);
+
+ // Open another task on the default display and verify no display focus switch as it's
+ // already on the default display.
+ changes.clear();
+ setupChange(changes, 789 /* taskId */, TRANSIT_OPEN, DEFAULT_DISPLAY,
+ true /* focused */);
+ when(info.getChanges()).thenReturn(changes);
+ mFocusTransitionObserver.onTransitionReady(binder, info, tx, tx);
+ verify(mListener, never()).onFocusedDisplayChanged(DEFAULT_DISPLAY);
+ }
+
+ private void setupChange(List<TransitionInfo.Change> changes, int taskId,
+ @TransitionMode int mode, int displayId, boolean focused) {
+ TransitionInfo.Change change = mock(TransitionInfo.Change.class);
+ RunningTaskInfo taskInfo = mock(RunningTaskInfo.class);
+ taskInfo.taskId = taskId;
+ taskInfo.isFocused = focused;
+ when(change.hasFlags(FLAG_MOVED_TO_TOP)).thenReturn(focused);
+ taskInfo.displayId = displayId;
+ when(change.getTaskInfo()).thenReturn(taskInfo);
+ when(change.getMode()).thenReturn(mode);
+ changes.add(change);
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
index 8f49de0a98fb..8dfdfb4dcbcf 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
@@ -100,7 +100,8 @@ public class HomeTransitionObserverTest extends ShellTestCase {
mHomeTransitionObserver = new HomeTransitionObserver(mContext, mMainExecutor);
mTransition = new Transitions(mContext, mock(ShellInit.class), mock(ShellController.class),
mOrganizer, mTransactionPool, mDisplayController, mMainExecutor,
- mMainHandler, mAnimExecutor, mHomeTransitionObserver);
+ mMainHandler, mAnimExecutor, mHomeTransitionObserver,
+ mock(FocusTransitionObserver.class));
mHomeTransitionObserver.setHomeTransitionListener(mTransition, mListener);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index aea14b900647..6cde0569796d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -158,7 +158,8 @@ public class ShellTransitionTests extends ShellTestCase {
ShellInit shellInit = mock(ShellInit.class);
final Transitions t = new Transitions(mContext, shellInit, mock(ShellController.class),
mOrganizer, mTransactionPool, createTestDisplayController(), mMainExecutor,
- mMainHandler, mAnimExecutor, mock(HomeTransitionObserver.class));
+ mMainHandler, mAnimExecutor, mock(HomeTransitionObserver.class),
+ mock(FocusTransitionObserver.class));
// One from Transitions, one from RootTaskDisplayAreaOrganizer
verify(shellInit).addInitCallback(any(), eq(t));
verify(shellInit).addInitCallback(any(), isA(RootTaskDisplayAreaOrganizer.class));
@@ -170,7 +171,8 @@ public class ShellTransitionTests extends ShellTestCase {
ShellController shellController = mock(ShellController.class);
final Transitions t = new Transitions(mContext, shellInit, shellController,
mOrganizer, mTransactionPool, createTestDisplayController(), mMainExecutor,
- mMainHandler, mAnimExecutor, mock(HomeTransitionObserver.class));
+ mMainHandler, mAnimExecutor, mock(HomeTransitionObserver.class),
+ mock(FocusTransitionObserver.class));
shellInit.init();
verify(shellController, times(1)).addExternalInterface(
eq(ShellSharedConstants.KEY_EXTRA_SHELL_SHELL_TRANSITIONS), any(), any());
@@ -1238,7 +1240,8 @@ public class ShellTransitionTests extends ShellTestCase {
final Transitions transitions =
new Transitions(mContext, shellInit, mock(ShellController.class), mOrganizer,
mTransactionPool, createTestDisplayController(), mMainExecutor,
- mMainHandler, mAnimExecutor, mock(HomeTransitionObserver.class));
+ mMainHandler, mAnimExecutor, mock(HomeTransitionObserver.class),
+ mock(FocusTransitionObserver.class));
final RecentsTransitionHandler recentsHandler =
new RecentsTransitionHandler(shellInit, mock(ShellTaskOrganizer.class), transitions,
mock(RecentTasksController.class), mock(HomeTransitionObserver.class));
@@ -1780,7 +1783,8 @@ public class ShellTransitionTests extends ShellTestCase {
ShellInit shellInit = new ShellInit(mMainExecutor);
final Transitions t = new Transitions(mContext, shellInit, mock(ShellController.class),
mOrganizer, mTransactionPool, createTestDisplayController(), mMainExecutor,
- mMainHandler, mAnimExecutor, mock(HomeTransitionObserver.class));
+ mMainHandler, mAnimExecutor, mock(HomeTransitionObserver.class),
+ mock(FocusTransitionObserver.class));
shellInit.init();
return t;
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/education/DesktopWindowingEducationTooltipControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/education/DesktopWindowingEducationTooltipControllerTest.kt
new file mode 100644
index 000000000000..5594981135b1
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/education/DesktopWindowingEducationTooltipControllerTest.kt
@@ -0,0 +1,237 @@
+/*
+ * 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.windowdecor.education
+
+import android.annotation.LayoutRes
+import android.content.Context
+import android.graphics.Point
+import android.testing.AndroidTestingRunner
+import android.testing.TestableContext
+import android.testing.TestableLooper
+import android.testing.TestableResources
+import android.view.MotionEvent
+import android.view.View
+import android.view.WindowManager
+import android.widget.TextView
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.R
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer
+import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationTooltipController.TooltipArrowDirection
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+
+@SmallTest
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@RunWith(AndroidTestingRunner::class)
+class DesktopWindowingEducationTooltipControllerTest : ShellTestCase() {
+ @Mock private lateinit var mockWindowManager: WindowManager
+ @Mock private lateinit var mockViewContainerFactory: AdditionalSystemViewContainer.Factory
+ private lateinit var testableResources: TestableResources
+ private lateinit var testableContext: TestableContext
+ private lateinit var tooltipController: DesktopWindowingEducationTooltipController
+ private val tooltipViewArgumentCaptor = argumentCaptor<View>()
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ testableContext = TestableContext(mContext)
+ testableResources =
+ testableContext.orCreateTestableResources.apply {
+ addOverride(R.dimen.desktop_windowing_education_tooltip_padding, 10)
+ }
+ testableContext.addMockSystemService(
+ Context.LAYOUT_INFLATER_SERVICE, context.getSystemService(Context.LAYOUT_INFLATER_SERVICE))
+ testableContext.addMockSystemService(WindowManager::class.java, mockWindowManager)
+ tooltipController =
+ DesktopWindowingEducationTooltipController(testableContext, mockViewContainerFactory)
+ }
+
+ @Test
+ fun showEducationTooltip_createsTooltipWithCorrectText() {
+ val tooltipText = "This is a tooltip"
+ val tooltipViewConfig = createTooltipConfig(tooltipText = tooltipText)
+
+ tooltipController.showEducationTooltip(tooltipViewConfig = tooltipViewConfig, taskId = 123)
+
+ verify(mockViewContainerFactory, times(1))
+ .create(
+ windowManagerWrapper = any(),
+ taskId = anyInt(),
+ x = anyInt(),
+ y = anyInt(),
+ width = anyInt(),
+ height = anyInt(),
+ flags = anyInt(),
+ view = tooltipViewArgumentCaptor.capture())
+ val tooltipTextView =
+ tooltipViewArgumentCaptor.lastValue.findViewById<TextView>(R.id.tooltip_text)
+ assertThat(tooltipTextView.text).isEqualTo(tooltipText)
+ }
+
+ @Test
+ fun showEducationTooltip_usesCorrectTaskIdForWindow() {
+ val tooltipViewConfig = createTooltipConfig()
+ val taskIdArgumentCaptor = argumentCaptor<Int>()
+
+ tooltipController.showEducationTooltip(tooltipViewConfig = tooltipViewConfig, taskId = 123)
+
+ verify(mockViewContainerFactory, times(1))
+ .create(
+ windowManagerWrapper = any(),
+ taskId = taskIdArgumentCaptor.capture(),
+ x = anyInt(),
+ y = anyInt(),
+ width = anyInt(),
+ height = anyInt(),
+ flags = anyInt(),
+ view = anyOrNull())
+ assertThat(taskIdArgumentCaptor.lastValue).isEqualTo(123)
+ }
+
+ @Test
+ fun showEducationTooltip_tooltipPointsUpwards_horizontallyPositionTooltip() {
+ val initialTooltipX = 0
+ val initialTooltipY = 0
+ val tooltipViewConfig =
+ createTooltipConfig(
+ arrowDirection = TooltipArrowDirection.UP,
+ tooltipViewGlobalCoordinates = Point(initialTooltipX, initialTooltipY))
+ val tooltipXArgumentCaptor = argumentCaptor<Int>()
+ val tooltipWidthArgumentCaptor = argumentCaptor<Int>()
+
+ tooltipController.showEducationTooltip(tooltipViewConfig = tooltipViewConfig, taskId = 123)
+
+ verify(mockViewContainerFactory, times(1))
+ .create(
+ windowManagerWrapper = any(),
+ taskId = anyInt(),
+ x = tooltipXArgumentCaptor.capture(),
+ y = anyInt(),
+ width = tooltipWidthArgumentCaptor.capture(),
+ height = anyInt(),
+ flags = anyInt(),
+ view = tooltipViewArgumentCaptor.capture())
+ val expectedTooltipX = initialTooltipX - tooltipWidthArgumentCaptor.lastValue / 2
+ assertThat(tooltipXArgumentCaptor.lastValue).isEqualTo(expectedTooltipX)
+ }
+
+ @Test
+ fun showEducationTooltip_tooltipPointsLeft_verticallyPositionTooltip() {
+ val initialTooltipX = 0
+ val initialTooltipY = 0
+ val tooltipViewConfig =
+ createTooltipConfig(
+ arrowDirection = TooltipArrowDirection.LEFT,
+ tooltipViewGlobalCoordinates = Point(initialTooltipX, initialTooltipY))
+ val tooltipYArgumentCaptor = argumentCaptor<Int>()
+ val tooltipHeightArgumentCaptor = argumentCaptor<Int>()
+
+ tooltipController.showEducationTooltip(tooltipViewConfig = tooltipViewConfig, taskId = 123)
+
+ verify(mockViewContainerFactory, times(1))
+ .create(
+ windowManagerWrapper = any(),
+ taskId = anyInt(),
+ x = anyInt(),
+ y = tooltipYArgumentCaptor.capture(),
+ width = anyInt(),
+ height = tooltipHeightArgumentCaptor.capture(),
+ flags = anyInt(),
+ view = tooltipViewArgumentCaptor.capture())
+ val expectedTooltipY = initialTooltipY - tooltipHeightArgumentCaptor.lastValue / 2
+ assertThat(tooltipYArgumentCaptor.lastValue).isEqualTo(expectedTooltipY)
+ }
+
+ @Test
+ fun showEducationTooltip_touchEventActionOutside_dismissActionPerformed() {
+ val mockLambda: () -> Unit = mock()
+ val tooltipViewConfig = createTooltipConfig(onDismissAction = mockLambda)
+
+ tooltipController.showEducationTooltip(tooltipViewConfig = tooltipViewConfig, taskId = 123)
+ verify(mockViewContainerFactory, times(1))
+ .create(
+ windowManagerWrapper = any(),
+ taskId = anyInt(),
+ x = anyInt(),
+ y = anyInt(),
+ width = anyInt(),
+ height = anyInt(),
+ flags = anyInt(),
+ view = tooltipViewArgumentCaptor.capture())
+ val motionEvent =
+ MotionEvent.obtain(
+ /* downTime= */ 0L,
+ /* eventTime= */ 0L,
+ MotionEvent.ACTION_OUTSIDE,
+ /* x= */ 0f,
+ /* y= */ 0f,
+ /* metaState= */ 0)
+ tooltipViewArgumentCaptor.lastValue.dispatchTouchEvent(motionEvent)
+
+ verify(mockLambda).invoke()
+ }
+
+ @Test
+ fun showEducationTooltip_tooltipClicked_onClickActionPerformed() {
+ val mockLambda: () -> Unit = mock()
+ val tooltipViewConfig = createTooltipConfig(onEducationClickAction = mockLambda)
+
+ tooltipController.showEducationTooltip(tooltipViewConfig = tooltipViewConfig, taskId = 123)
+ verify(mockViewContainerFactory, times(1))
+ .create(
+ windowManagerWrapper = any(),
+ taskId = anyInt(),
+ x = anyInt(),
+ y = anyInt(),
+ width = anyInt(),
+ height = anyInt(),
+ flags = anyInt(),
+ view = tooltipViewArgumentCaptor.capture())
+ tooltipViewArgumentCaptor.lastValue.performClick()
+
+ verify(mockLambda).invoke()
+ }
+
+ private fun createTooltipConfig(
+ @LayoutRes tooltipViewLayout: Int = R.layout.desktop_windowing_education_top_arrow_tooltip,
+ tooltipViewGlobalCoordinates: Point = Point(0, 0),
+ tooltipText: String = "This is a tooltip",
+ arrowDirection: TooltipArrowDirection = TooltipArrowDirection.UP,
+ onEducationClickAction: () -> Unit = {},
+ onDismissAction: () -> Unit = {}
+ ) =
+ DesktopWindowingEducationTooltipController.EducationViewConfig(
+ tooltipViewLayout = tooltipViewLayout,
+ tooltipViewGlobalCoordinates = tooltipViewGlobalCoordinates,
+ tooltipText = tooltipText,
+ arrowDirection = arrowDirection,
+ onEducationClickAction = onEducationClickAction,
+ onDismissAction = onDismissAction,
+ )
+}
diff --git a/libs/appfunctions/api/current.txt b/libs/appfunctions/api/current.txt
index 504e3290b0ae..bb0fc41acd30 100644
--- a/libs/appfunctions/api/current.txt
+++ b/libs/appfunctions/api/current.txt
@@ -3,13 +3,20 @@ package com.google.android.appfunctions.sidecar {
public final class AppFunctionManager {
ctor public AppFunctionManager(android.content.Context);
- method public void executeAppFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>);
+ method public void executeAppFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>);
+ method @Deprecated public void executeAppFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>);
+ method public void isAppFunctionEnabled(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>);
+ method public void setAppFunctionEnabled(@NonNull String, int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,java.lang.Exception>);
+ field public static final int APP_FUNCTION_STATE_DEFAULT = 0; // 0x0
+ field public static final int APP_FUNCTION_STATE_DISABLED = 2; // 0x2
+ field public static final int APP_FUNCTION_STATE_ENABLED = 1; // 0x1
}
public abstract class AppFunctionService extends android.app.Service {
ctor public AppFunctionService();
method @NonNull public final android.os.IBinder onBind(@Nullable android.content.Intent);
- method @MainThread public abstract void onExecuteFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>);
+ method @MainThread public void onExecuteFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>);
+ method @Deprecated @MainThread public abstract void onExecuteFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>);
field @NonNull public static final String BIND_APP_FUNCTION_SERVICE = "android.permission.BIND_APP_FUNCTION_SERVICE";
field @NonNull public static final String SERVICE_INTERFACE = "android.app.appfunctions.AppFunctionService";
}
@@ -39,6 +46,7 @@ package com.google.android.appfunctions.sidecar {
field public static final String PROPERTY_RETURN_VALUE = "returnValue";
field public static final int RESULT_APP_UNKNOWN_ERROR = 2; // 0x2
field public static final int RESULT_DENIED = 1; // 0x1
+ field public static final int RESULT_DISABLED = 6; // 0x6
field public static final int RESULT_INTERNAL_ERROR = 3; // 0x3
field public static final int RESULT_INVALID_ARGUMENT = 4; // 0x4
field public static final int RESULT_OK = 0; // 0x0
diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java
index b1dd4676a35e..d660926575d1 100644
--- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java
+++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java
@@ -16,15 +16,22 @@
package com.google.android.appfunctions.sidecar;
+import android.Manifest;
import android.annotation.CallbackExecutor;
+import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.annotation.UserHandleAware;
import android.content.Context;
+import android.os.CancellationSignal;
+import android.os.OutcomeReceiver;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
-
/**
* Provides app functions related functionalities.
*
@@ -37,6 +44,39 @@ import java.util.function.Consumer;
// TODO(b/357551503): Implement get and set enabled app function APIs.
// TODO(b/367329899): Add sidecar library to Android B builds.
public final class AppFunctionManager {
+ /**
+ * The default state of the app function. Call {@link #setAppFunctionEnabled} with this to reset
+ * enabled state to the default value.
+ */
+ public static final int APP_FUNCTION_STATE_DEFAULT = 0;
+
+ /**
+ * The app function is enabled. To enable an app function, call {@link #setAppFunctionEnabled}
+ * with this value.
+ */
+ public static final int APP_FUNCTION_STATE_ENABLED = 1;
+
+ /**
+ * The app function is disabled. To disable an app function, call {@link #setAppFunctionEnabled}
+ * with this value.
+ */
+ public static final int APP_FUNCTION_STATE_DISABLED = 2;
+
+ /**
+ * The enabled state of the app function.
+ *
+ * @hide
+ */
+ @IntDef(
+ prefix = {"APP_FUNCTION_STATE_"},
+ value = {
+ APP_FUNCTION_STATE_DEFAULT,
+ APP_FUNCTION_STATE_ENABLED,
+ APP_FUNCTION_STATE_DISABLED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface EnabledState {}
+
private final android.app.appfunctions.AppFunctionManager mManager;
private final Context mContext;
@@ -45,7 +85,7 @@ public final class AppFunctionManager {
*
* @param context A {@link Context}.
* @throws java.lang.IllegalStateException if the underlying {@link
- * android.app.appfunctions.AppFunctionManager} is not found.
+ * android.app.appfunctions.AppFunctionManager} is not found.
*/
public AppFunctionManager(Context context) {
mContext = Objects.requireNonNull(context);
@@ -66,6 +106,7 @@ public final class AppFunctionManager {
public void executeAppFunction(
@NonNull ExecuteAppFunctionRequest sidecarRequest,
@NonNull @CallbackExecutor Executor executor,
+ @NonNull CancellationSignal cancellationSignal,
@NonNull Consumer<ExecuteAppFunctionResponse> callback) {
Objects.requireNonNull(sidecarRequest);
Objects.requireNonNull(executor);
@@ -74,9 +115,100 @@ public final class AppFunctionManager {
android.app.appfunctions.ExecuteAppFunctionRequest platformRequest =
SidecarConverter.getPlatformExecuteAppFunctionRequest(sidecarRequest);
mManager.executeAppFunction(
- platformRequest, executor, (platformResponse) -> {
- callback.accept(SidecarConverter.getSidecarExecuteAppFunctionResponse(
- platformResponse));
+ platformRequest,
+ executor,
+ cancellationSignal,
+ (platformResponse) -> {
+ callback.accept(
+ SidecarConverter.getSidecarExecuteAppFunctionResponse(
+ platformResponse));
});
}
+
+ /**
+ * Executes the app function.
+ *
+ * <p>Proxies request and response to the underlying {@link
+ * android.app.appfunctions.AppFunctionManager#executeAppFunction}, converting the request and
+ * response in the appropriate type required by the function.
+ *
+ * @deprecated Use {@link #executeAppFunction(ExecuteAppFunctionRequest, Executor,
+ * CancellationSignal, Consumer)} instead. This method will be removed once usage references
+ * are updated.
+ */
+ @Deprecated
+ public void executeAppFunction(
+ @NonNull ExecuteAppFunctionRequest sidecarRequest,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull Consumer<ExecuteAppFunctionResponse> callback) {
+ Objects.requireNonNull(sidecarRequest);
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+
+ executeAppFunction(
+ sidecarRequest,
+ executor,
+ new CancellationSignal(),
+ callback);
+ }
+
+ /**
+ * Returns a boolean through a callback, indicating whether the app function is enabled.
+ *
+ * <p>* This method can only check app functions owned by the caller, or those where the caller
+ * has visibility to the owner package and holds either the {@link
+ * Manifest.permission#EXECUTE_APP_FUNCTIONS} or {@link
+ * Manifest.permission#EXECUTE_APP_FUNCTIONS_TRUSTED} permission.
+ *
+ * <p>If operation fails, the callback's {@link OutcomeReceiver#onError} is called with errors:
+ *
+ * <ul>
+ * <li>{@link IllegalArgumentException}, if the function is not found or the caller does not
+ * have access to it.
+ * </ul>
+ *
+ * @param functionIdentifier the identifier of the app function to check (unique within the
+ * target package) and in most cases, these are automatically generated by the AppFunctions
+ * SDK
+ * @param targetPackage the package name of the app function's owner
+ * @param executor the executor to run the request
+ * @param callback the callback to receive the function enabled check result
+ */
+ public void isAppFunctionEnabled(
+ @NonNull String functionIdentifier,
+ @NonNull String targetPackage,
+ @NonNull Executor executor,
+ @NonNull OutcomeReceiver<Boolean, Exception> callback) {
+ mManager.isAppFunctionEnabled(functionIdentifier, targetPackage, executor, callback);
+ }
+
+ /**
+ * Sets the enabled state of the app function owned by the calling package.
+ *
+ * <p>If operation fails, the callback's {@link OutcomeReceiver#onError} is called with errors:
+ *
+ * <ul>
+ * <li>{@link IllegalArgumentException}, if the function is not found or the caller does not
+ * have access to it.
+ * </ul>
+ *
+ * @param functionIdentifier the identifier of the app function to enable (unique within the
+ * calling package). In most cases, identifiers are automatically generated by the
+ * AppFunctions SDK
+ * @param newEnabledState the new state of the app function
+ * @param executor the executor to run the callback
+ * @param callback the callback to receive the result of the function enablement. The call was
+ * successful if no exception was thrown.
+ */
+ // Constants in @EnabledState should always mirror those in
+ // android.app.appfunctions.AppFunctionManager.
+ @SuppressLint("WrongConstant")
+ @UserHandleAware
+ public void setAppFunctionEnabled(
+ @NonNull String functionIdentifier,
+ @EnabledState int newEnabledState,
+ @NonNull Executor executor,
+ @NonNull OutcomeReceiver<Void, Exception> callback) {
+ mManager.setAppFunctionEnabled(functionIdentifier, newEnabledState, executor, callback);
+ }
}
diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java
index 65959dfdf561..6023c977bd76 100644
--- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java
+++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java
@@ -25,6 +25,7 @@ import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
+import android.os.CancellationSignal;
import java.util.function.Consumer;
@@ -69,10 +70,11 @@ public abstract class AppFunctionService extends Service {
private final Binder mBinder =
android.app.appfunctions.AppFunctionService.createBinder(
/* context= */ this,
- /* onExecuteFunction= */ (platformRequest, callback) -> {
+ /* onExecuteFunction= */ (platformRequest, cancellationSignal, callback) -> {
AppFunctionService.this.onExecuteFunction(
SidecarConverter.getSidecarExecuteAppFunctionRequest(
platformRequest),
+ cancellationSignal,
(sidecarResponse) -> {
callback.accept(
SidecarConverter.getPlatformExecuteAppFunctionResponse(
@@ -105,9 +107,42 @@ public abstract class AppFunctionService extends Service {
* result using the callback, no matter if the execution was successful or not.
*
* @param request The function execution request.
+ * @param cancellationSignal A {@link CancellationSignal} to cancel the request.
* @param callback A callback to report back the result.
*/
@MainThread
+ public void onExecuteFunction(
+ @NonNull ExecuteAppFunctionRequest request,
+ @NonNull CancellationSignal cancellationSignal,
+ @NonNull Consumer<ExecuteAppFunctionResponse> callback) {
+ onExecuteFunction(request, callback);
+ }
+
+ /**
+ * Called by the system to execute a specific app function.
+ *
+ * <p>This method is triggered when the system requests your AppFunctionService to handle a
+ * particular function you have registered and made available.
+ *
+ * <p>To ensure proper routing of function requests, assign a unique identifier to each
+ * function. This identifier doesn't need to be globally unique, but it must be unique within
+ * your app. For example, a function to order food could be identified as "orderFood". In most
+ * cases this identifier should come from the ID automatically generated by the AppFunctions
+ * SDK. You can determine the specific function to invoke by calling {@link
+ * ExecuteAppFunctionRequest#getFunctionIdentifier()}.
+ *
+ * <p>This method is always triggered in the main thread. You should run heavy tasks on a worker
+ * thread and dispatch the result with the given callback. You should always report back the
+ * result using the callback, no matter if the execution was successful or not.
+ *
+ * @param request The function execution request.
+ * @param callback A callback to report back the result.
+ *
+ * @deprecated Use {@link #onExecuteFunction(ExecuteAppFunctionRequest, CancellationSignal,
+ * Consumer)} instead. This method will be removed once usage references are updated.
+ */
+ @MainThread
+ @Deprecated
public abstract void onExecuteFunction(
@NonNull ExecuteAppFunctionRequest request,
@NonNull Consumer<ExecuteAppFunctionResponse> callback);
diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java
index 60c25fae58d1..c7ce95bab7a5 100644
--- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java
+++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java
@@ -76,6 +76,9 @@ public final class ExecuteAppFunctionResponse {
/** The operation was timed out. */
public static final int RESULT_TIMED_OUT = 5;
+ /** The caller tried to execute a disabled app function. */
+ public static final int RESULT_DISABLED = 6;
+
/** The result code of the app function execution. */
@ResultCode private final int mResultCode;
@@ -234,6 +237,7 @@ public final class ExecuteAppFunctionResponse {
RESULT_INTERNAL_ERROR,
RESULT_INVALID_ARGUMENT,
RESULT_TIMED_OUT,
+ RESULT_DISABLED
})
@Retention(RetentionPolicy.SOURCE)
public @interface ResultCode {}
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 9e825fb350d6..7c150862a422 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -619,8 +619,11 @@ bool SkiaCanvas::useGainmapShader(Bitmap& bitmap) {
// If it's an unknown colortype then it's not a bitmap-backed canvas
if (info.colorType() == SkColorType::kUnknown_SkColorType) return true;
+ auto colorSpace = info.colorSpace();
+ // If we don't have a colorspace, we can't apply a gainmap
+ if (!colorSpace) return false;
skcms_TransferFunction tfn;
- info.colorSpace()->transferFn(&tfn);
+ colorSpace->transferFn(&tfn);
auto transferType = skcms_TransferFunction_getType(&tfn);
switch (transferType) {
diff --git a/libs/hwui/hwui/MinikinSkia.cpp b/libs/hwui/hwui/MinikinSkia.cpp
index bbb142014ed8..f0bcfe537998 100644
--- a/libs/hwui/hwui/MinikinSkia.cpp
+++ b/libs/hwui/hwui/MinikinSkia.cpp
@@ -36,7 +36,7 @@ namespace android {
MinikinFontSkia::MinikinFontSkia(sk_sp<SkTypeface> typeface, int sourceId, const void* fontData,
size_t fontSize, std::string_view filePath, int ttcIndex,
- const std::vector<minikin::FontVariation>& axes)
+ const minikin::VariationSettings& axes)
: mTypeface(std::move(typeface))
, mSourceId(sourceId)
, mFontData(fontData)
@@ -123,12 +123,12 @@ int MinikinFontSkia::GetFontIndex() const {
return mTtcIndex;
}
-const std::vector<minikin::FontVariation>& MinikinFontSkia::GetAxes() const {
+const minikin::VariationSettings& MinikinFontSkia::GetAxes() const {
return mAxes;
}
std::shared_ptr<minikin::MinikinFont> MinikinFontSkia::createFontWithVariation(
- const std::vector<minikin::FontVariation>& variations) const {
+ const minikin::VariationSettings& variations) const {
SkFontArguments args;
std::vector<SkFontArguments::VariationPosition::Coordinate> skVariation;
diff --git a/libs/hwui/hwui/MinikinSkia.h b/libs/hwui/hwui/MinikinSkia.h
index de9a5c2af0aa..7fe5978bfda3 100644
--- a/libs/hwui/hwui/MinikinSkia.h
+++ b/libs/hwui/hwui/MinikinSkia.h
@@ -32,7 +32,7 @@ class ANDROID_API MinikinFontSkia : public minikin::MinikinFont {
public:
MinikinFontSkia(sk_sp<SkTypeface> typeface, int sourceId, const void* fontData, size_t fontSize,
std::string_view filePath, int ttcIndex,
- const std::vector<minikin::FontVariation>& axes);
+ const minikin::VariationSettings& axes);
float GetHorizontalAdvance(uint32_t glyph_id, const minikin::MinikinPaint& paint,
const minikin::FontFakery& fakery) const override;
@@ -59,9 +59,9 @@ public:
size_t GetFontSize() const;
int GetFontIndex() const;
const std::string& getFilePath() const { return mFilePath; }
- const std::vector<minikin::FontVariation>& GetAxes() const;
+ const minikin::VariationSettings& GetAxes() const;
std::shared_ptr<minikin::MinikinFont> createFontWithVariation(
- const std::vector<minikin::FontVariation>&) const;
+ const minikin::VariationSettings&) const;
int GetSourceId() const override { return mSourceId; }
static uint32_t packFontFlags(const SkFont&);
@@ -80,7 +80,7 @@ private:
const void* mFontData;
size_t mFontSize;
int mTtcIndex;
- std::vector<minikin::FontVariation> mAxes;
+ minikin::VariationSettings mAxes;
std::string mFilePath;
};
diff --git a/libs/hwui/hwui/Typeface.cpp b/libs/hwui/hwui/Typeface.cpp
index a9d1a2aed8cc..2d812d675fdc 100644
--- a/libs/hwui/hwui/Typeface.cpp
+++ b/libs/hwui/hwui/Typeface.cpp
@@ -92,8 +92,8 @@ Typeface* Typeface::createAbsolute(Typeface* base, int weight, bool italic) {
return result;
}
-Typeface* Typeface::createFromTypefaceWithVariation(
- Typeface* src, const std::vector<minikin::FontVariation>& variations) {
+Typeface* Typeface::createFromTypefaceWithVariation(Typeface* src,
+ const minikin::VariationSettings& variations) {
const Typeface* resolvedFace = Typeface::resolveDefault(src);
Typeface* result = new Typeface();
if (result != nullptr) {
@@ -192,9 +192,8 @@ void Typeface::setRobotoTypefaceForTest() {
sk_sp<SkTypeface> typeface = fm->makeFromStream(std::move(fontData));
LOG_ALWAYS_FATAL_IF(typeface == nullptr, "Failed to make typeface from %s", kRobotoFont);
- std::shared_ptr<minikin::MinikinFont> font =
- std::make_shared<MinikinFontSkia>(std::move(typeface), 0, data, st.st_size, kRobotoFont,
- 0, std::vector<minikin::FontVariation>());
+ std::shared_ptr<minikin::MinikinFont> font = std::make_shared<MinikinFontSkia>(
+ std::move(typeface), 0, data, st.st_size, kRobotoFont, 0, minikin::VariationSettings());
std::vector<std::shared_ptr<minikin::Font>> fonts;
fonts.push_back(minikin::Font::Builder(font).build());
diff --git a/libs/hwui/hwui/Typeface.h b/libs/hwui/hwui/Typeface.h
index 565136e53676..2c96c1ad80fe 100644
--- a/libs/hwui/hwui/Typeface.h
+++ b/libs/hwui/hwui/Typeface.h
@@ -74,8 +74,8 @@ public:
static Typeface* createRelative(Typeface* src, Style desiredStyle);
static Typeface* createAbsolute(Typeface* base, int weight, bool italic);
- static Typeface* createFromTypefaceWithVariation(
- Typeface* src, const std::vector<minikin::FontVariation>& variations);
+ static Typeface* createFromTypefaceWithVariation(Typeface* src,
+ const minikin::VariationSettings& variations);
static Typeface* createFromFamilies(
std::vector<std::shared_ptr<minikin::FontFamily>>&& families, int weight, int italic,
diff --git a/libs/hwui/jni/FontFamily.cpp b/libs/hwui/jni/FontFamily.cpp
index e6d790f56d0f..9922ff393e55 100644
--- a/libs/hwui/jni/FontFamily.cpp
+++ b/libs/hwui/jni/FontFamily.cpp
@@ -133,9 +133,9 @@ static bool addSkTypeface(NativeFamilyBuilder* builder, sk_sp<SkData>&& data, in
builder->axes.clear();
return false;
}
- std::shared_ptr<minikin::MinikinFont> minikinFont =
- std::make_shared<MinikinFontSkia>(std::move(face), fonts::getNewSourceId(), fontPtr,
- fontSize, "", ttcIndex, builder->axes);
+ std::shared_ptr<minikin::MinikinFont> minikinFont = std::make_shared<MinikinFontSkia>(
+ std::move(face), fonts::getNewSourceId(), fontPtr, fontSize, "", ttcIndex,
+ minikin::VariationSettings(builder->axes, false));
minikin::Font::Builder fontBuilder(minikinFont);
if (weight != RESOLVE_BY_FONT_TABLE) {
diff --git a/libs/hwui/jni/PathIterator.cpp b/libs/hwui/jni/PathIterator.cpp
index 3884342d8d37..e9de6555935d 100644
--- a/libs/hwui/jni/PathIterator.cpp
+++ b/libs/hwui/jni/PathIterator.cpp
@@ -20,6 +20,7 @@
#include "GraphicsJNI.h"
#include "SkPath.h"
#include "SkPoint.h"
+#include "graphics_jni_helpers.h"
namespace android {
@@ -36,6 +37,18 @@ public:
return reinterpret_cast<jlong>(new SkPath::RawIter(*path));
}
+ // A variant of 'next' (below) that is compatible with the host JVM.
+ static jint nextHost(JNIEnv* env, jclass clazz, jlong iteratorHandle, jfloatArray pointsArray) {
+ jfloat* points = env->GetFloatArrayElements(pointsArray, 0);
+#ifdef __ANDROID__
+ jint result = next(iteratorHandle, reinterpret_cast<jlong>(points));
+#else
+ jint result = next(env, clazz, iteratorHandle, reinterpret_cast<jlong>(points));
+#endif
+ env->ReleaseFloatArrayElements(pointsArray, points, 0);
+ return result;
+ }
+
// ---------------- @CriticalNative -------------------------
static jint peek(CRITICAL_JNI_PARAMS_COMMA jlong iteratorHandle) {
@@ -72,6 +85,7 @@ static const JNINativeMethod methods[] = {
{"nPeek", "(J)I", (void*)SkPathIteratorGlue::peek},
{"nNext", "(JJ)I", (void*)SkPathIteratorGlue::next},
+ {"nNextHost", "(J[F)I", (void*)SkPathIteratorGlue::nextHost},
};
int register_android_graphics_PathIterator(JNIEnv* env) {
diff --git a/libs/hwui/jni/Typeface.cpp b/libs/hwui/jni/Typeface.cpp
index 209b35c5537c..0f458dde8b07 100644
--- a/libs/hwui/jni/Typeface.cpp
+++ b/libs/hwui/jni/Typeface.cpp
@@ -80,7 +80,8 @@ static jlong Typeface_createFromTypefaceWithVariation(JNIEnv* env, jobject, jlon
AxisHelper axis(env, axisObject);
variations.push_back(minikin::FontVariation(axis.getTag(), axis.getStyleValue()));
}
- return toJLong(Typeface::createFromTypefaceWithVariation(toTypeface(familyHandle), variations));
+ return toJLong(Typeface::createFromTypefaceWithVariation(
+ toTypeface(familyHandle), minikin::VariationSettings(variations, false /* sorted */)));
}
static jlong Typeface_createWeightAlias(JNIEnv* env, jobject, jlong familyHandle, jint weight) {
@@ -273,7 +274,7 @@ void MinikinFontSkiaFactory::write(minikin::BufferWriter* writer,
const std::string& path = typeface->GetFontPath();
writer->writeString(path);
writer->write<int>(typeface->GetFontIndex());
- const std::vector<minikin::FontVariation>& axes = typeface->GetAxes();
+ const minikin::VariationSettings& axes = typeface->GetAxes();
writer->writeArray<minikin::FontVariation>(axes.data(), axes.size());
bool hasVerity = getVerity(path);
writer->write<int8_t>(static_cast<int8_t>(hasVerity));
diff --git a/libs/hwui/jni/fonts/Font.cpp b/libs/hwui/jni/fonts/Font.cpp
index f405abaaf5b4..6a05b6c2626c 100644
--- a/libs/hwui/jni/fonts/Font.cpp
+++ b/libs/hwui/jni/fonts/Font.cpp
@@ -142,7 +142,7 @@ static jlong Font_Builder_clone(JNIEnv* env, jobject clazz, jlong fontPtr, jlong
std::shared_ptr<minikin::MinikinFont> newMinikinFont = std::make_shared<MinikinFontSkia>(
std::move(newTypeface), minikinSkia->GetSourceId(), minikinSkia->GetFontData(),
minikinSkia->GetFontSize(), minikinSkia->getFilePath(), minikinSkia->GetFontIndex(),
- builder->axes);
+ minikin::VariationSettings(builder->axes, false));
std::shared_ptr<minikin::Font> newFont = minikin::Font::Builder(newMinikinFont)
.setWeight(weight)
.setSlant(static_cast<minikin::FontStyle::Slant>(italic))
@@ -303,7 +303,7 @@ static jlong Font_getAxisInfo(CRITICAL_JNI_PARAMS_COMMA jlong fontPtr, jint inde
var = reader.readArray<minikin::FontVariation>().first[index];
} else {
const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->baseTypeface();
- var = minikinFont->GetAxes().at(index);
+ var = minikinFont->GetAxes()[index];
}
uint32_t floatBinary = *reinterpret_cast<const uint32_t*>(&var.value);
return (static_cast<uint64_t>(var.axisTag) << 32) | static_cast<uint64_t>(floatBinary);
diff --git a/media/java/android/media/tv/flags/media_tv.aconfig b/media/java/android/media/tv/flags/media_tv.aconfig
index c814c95e09d9..10423b9c1f0f 100644
--- a/media/java/android/media/tv/flags/media_tv.aconfig
+++ b/media/java/android/media/tv/flags/media_tv.aconfig
@@ -56,3 +56,11 @@ flag {
description: "Enhance HDMI-CEC power state and activeness transitions"
bug: "332780751"
}
+
+flag {
+ name: "media_quality_fw"
+ is_exported: true
+ namespace: "media_tv"
+ description: "Media Quality V1.0 APIs for Android W"
+ bug: "348412562"
+}
diff --git a/media/jni/android_media_ImageWriter.cpp b/media/jni/android_media_ImageWriter.cpp
index 6776f611559c..33650d91e6a3 100644
--- a/media/jni/android_media_ImageWriter.cpp
+++ b/media/jni/android_media_ImageWriter.cpp
@@ -735,10 +735,15 @@ static void ImageWriter_queueImage(JNIEnv* env, jobject thiz, jlong nativeCtx, j
}
static status_t attachAndQeueuGraphicBuffer(JNIEnv* env, JNIImageWriterContext *ctx,
- sp<Surface> surface, sp<GraphicBuffer> gb, jlong timestampNs, jint dataSpace,
+ sp<GraphicBuffer> gb, jlong timestampNs, jint dataSpace,
jint left, jint top, jint right, jint bottom, jint transform, jint scalingMode) {
status_t res = OK;
// Step 1. Attach Image
+ sp<Surface> surface = ctx->getProducer();
+ if (surface == nullptr) {
+ jniThrowException(env, "java/lang/IllegalStateException",
+ "Producer surface is null, ImageWriter seems already closed");
+ }
res = surface->attachBuffer(gb.get());
if (res != OK) {
ALOGE("Attach image failed: %s (%d)", strerror(-res), res);
@@ -835,7 +840,6 @@ static jint ImageWriter_attachAndQueueImage(JNIEnv* env, jobject thiz, jlong nat
return -1;
}
- sp<Surface> surface = ctx->getProducer();
if (isFormatOpaque(ctx->getBufferFormat()) != isFormatOpaque(nativeHalFormat)) {
jniThrowException(env, "java/lang/IllegalStateException",
"Trying to attach an opaque image into a non-opaque ImageWriter, or vice versa");
@@ -851,7 +855,7 @@ static jint ImageWriter_attachAndQueueImage(JNIEnv* env, jobject thiz, jlong nat
return -1;
}
- return attachAndQeueuGraphicBuffer(env, ctx, surface, buffer->mGraphicBuffer, timestampNs,
+ return attachAndQeueuGraphicBuffer(env, ctx, buffer->mGraphicBuffer, timestampNs,
dataSpace, left, top, right, bottom, transform, scalingMode);
}
@@ -866,7 +870,6 @@ static jint ImageWriter_attachAndQueueGraphicBuffer(JNIEnv* env, jobject thiz, j
return -1;
}
- sp<Surface> surface = ctx->getProducer();
if (isFormatOpaque(ctx->getBufferFormat()) != isFormatOpaque(nativeHalFormat)) {
jniThrowException(env, "java/lang/IllegalStateException",
"Trying to attach an opaque image into a non-opaque ImageWriter, or vice versa");
@@ -880,7 +883,8 @@ static jint ImageWriter_attachAndQueueGraphicBuffer(JNIEnv* env, jobject thiz, j
"Trying to attach an invalid graphic buffer");
return -1;
}
- return attachAndQeueuGraphicBuffer(env, ctx, surface, graphicBuffer, timestampNs,
+
+ return attachAndQeueuGraphicBuffer(env, ctx, graphicBuffer, timestampNs,
dataSpace, left, top, right, bottom, transform, scalingMode);
}
diff --git a/native/android/system_fonts.cpp b/native/android/system_fonts.cpp
index 91f78ce6f950..0c07b2acbb0c 100644
--- a/native/android/system_fonts.cpp
+++ b/native/android/system_fonts.cpp
@@ -327,7 +327,7 @@ AFont* _Nonnull AFontMatcher_match(
result->mWeight = font->style().weight();
result->mItalic = font->style().slant() == minikin::FontStyle::Slant::ITALIC;
result->mCollectionIndex = minikinFontSkia->GetFontIndex();
- const std::vector<minikin::FontVariation>& axes = minikinFontSkia->GetAxes();
+ const minikin::VariationSettings& axes = minikinFontSkia->GetAxes();
result->mAxes.reserve(axes.size());
for (auto axis : axes) {
result->mAxes.push_back(std::make_pair(axis.axisTag, axis.value));
diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt
index d17a9b62d02d..94231b0facdb 100644
--- a/nfc/api/system-current.txt
+++ b/nfc/api/system-current.txt
@@ -58,10 +58,15 @@ package android.nfc {
@FlaggedApi("android.nfc.nfc_oem_extension") public final class NfcOemExtension {
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void clearPreference();
method @FlaggedApi("android.nfc.nfc_oem_extension") @NonNull public java.util.List<java.lang.String> getActiveNfceeList();
+ method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean hasUserEnabledNfc();
+ method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean isTagPresent();
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void maybeTriggerFirmwareUpdate();
+ method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void pausePolling(int);
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void registerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcOemExtension.Callback);
+ method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void resumePolling();
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void setControllerAlwaysOnMode(int);
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void synchronizeScreenState();
+ method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void triggerInitialization();
method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void unregisterCallback(@NonNull android.nfc.NfcOemExtension.Callback);
field @FlaggedApi("android.nfc.nfc_oem_extension") public static final int DISABLE = 0; // 0x0
field @FlaggedApi("android.nfc.nfc_oem_extension") public static final int ENABLE_DEFAULT = 1; // 0x1
diff --git a/nfc/java/android/nfc/INfcAdapter.aidl b/nfc/java/android/nfc/INfcAdapter.aidl
index 246efc7ca557..a166b283f55b 100644
--- a/nfc/java/android/nfc/INfcAdapter.aidl
+++ b/nfc/java/android/nfc/INfcAdapter.aidl
@@ -114,4 +114,7 @@ interface INfcAdapter
void setScreenState();
void checkFirmware();
List<String> fetchActiveNfceeList();
+ void triggerInitialization();
+ boolean getSettingStatus();
+ boolean isTagPresent();
}
diff --git a/nfc/java/android/nfc/NfcOemExtension.java b/nfc/java/android/nfc/NfcOemExtension.java
index 011c60b080f8..6d5c069bca3a 100644
--- a/nfc/java/android/nfc/NfcOemExtension.java
+++ b/nfc/java/android/nfc/NfcOemExtension.java
@@ -471,6 +471,58 @@ public final class NfcOemExtension {
NfcAdapter.callService(() -> NfcAdapter.sService.setControllerAlwaysOn(mode));
}
+ /**
+ * Triggers NFC initialization. If OEM extension is registered
+ * (indicated via `enable_oem_extension` NFC overlay), the NFC stack initialization at bootup
+ * is delayed until the OEM extension app triggers the initialization via this call.
+ */
+ @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
+ @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ public void triggerInitialization() {
+ NfcAdapter.callService(() -> NfcAdapter.sService.triggerInitialization());
+ }
+
+ /**
+ * Gets the last user toggle status.
+ * @return true if NFC is set to ON, false otherwise
+ */
+ @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
+ @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ public boolean hasUserEnabledNfc() {
+ return NfcAdapter.callServiceReturn(() -> NfcAdapter.sService.getSettingStatus(), false);
+ }
+
+ /**
+ * Checks if the tag is present or not.
+ * @return true if the tag is present, false otherwise
+ */
+ @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
+ @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ public boolean isTagPresent() {
+ return NfcAdapter.callServiceReturn(() -> NfcAdapter.sService.isTagPresent(), false);
+ }
+
+ /**
+ * Pauses NFC tag reader mode polling for a {@code timeoutInMs} millisecond. If polling must be
+ * resumed before timeout, use {@link #resumePolling()}.
+ * @param timeoutInMs the pause polling duration in millisecond
+ */
+ @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
+ @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ public void pausePolling(int timeoutInMs) {
+ NfcAdapter.callService(() -> NfcAdapter.sService.pausePolling(timeoutInMs));
+ }
+
+ /**
+ * Resumes default NFC tag reader mode polling for the current device state if polling is
+ * paused. Calling this while polling is not paused is a no-op.
+ */
+ @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
+ @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ public void resumePolling() {
+ NfcAdapter.callService(() -> NfcAdapter.sService.resumePolling());
+ }
+
private final class NfcOemExtensionCallback extends INfcOemExtensionCallback.Stub {
@Override
diff --git a/packages/SettingsLib/ActionButtonsPreference/res/layout-v35/settingslib_expressive_action_buttons.xml b/packages/SettingsLib/ActionButtonsPreference/res/layout-v35/settingslib_expressive_action_buttons.xml
index fc63c0f9ab93..3e73ebd4880e 100644
--- a/packages/SettingsLib/ActionButtonsPreference/res/layout-v35/settingslib_expressive_action_buttons.xml
+++ b/packages/SettingsLib/ActionButtonsPreference/res/layout-v35/settingslib_expressive_action_buttons.xml
@@ -35,11 +35,13 @@
style="@style/SettingsLibActionButton.Expressive"
android:layout_width="@dimen/settingslib_expressive_space_large3"
android:layout_height="@dimen/settingslib_expressive_space_medium5"
- android:layout_gravity="center_horizontal" />
+ android:layout_gravity="center_horizontal"
+ android:importantForAccessibility="no"/>
<TextView
android:id="@+id/text1"
style="@style/SettingsLibActionButton.Expressive.Label"
- android:layout_marginTop="@dimen/settingslib_expressive_space_extrasmall3"/>
+ android:layout_marginTop="@dimen/settingslib_expressive_space_extrasmall3"
+ android:importantForAccessibility="no"/>
</LinearLayout>
@@ -55,11 +57,13 @@
style="@style/SettingsLibActionButton.Expressive"
android:layout_width="@dimen/settingslib_expressive_space_large3"
android:layout_height="@dimen/settingslib_expressive_space_medium5"
- android:layout_gravity="center_horizontal" />
+ android:layout_gravity="center_horizontal"
+ android:importantForAccessibility="no"/>
<TextView
android:id="@+id/text2"
style="@style/SettingsLibActionButton.Expressive.Label"
- android:layout_marginTop="@dimen/settingslib_expressive_space_extrasmall3"/>
+ android:layout_marginTop="@dimen/settingslib_expressive_space_extrasmall3"
+ android:importantForAccessibility="no"/>
</LinearLayout>
@@ -75,11 +79,13 @@
style="@style/SettingsLibActionButton.Expressive"
android:layout_width="@dimen/settingslib_expressive_space_large3"
android:layout_height="@dimen/settingslib_expressive_space_medium5"
- android:layout_gravity="center_horizontal" />
+ android:layout_gravity="center_horizontal"
+ android:importantForAccessibility="no"/>
<TextView
android:id="@+id/text3"
style="@style/SettingsLibActionButton.Expressive.Label"
- android:layout_marginTop="@dimen/settingslib_expressive_space_extrasmall3"/>
+ android:layout_marginTop="@dimen/settingslib_expressive_space_extrasmall3"
+ android:importantForAccessibility="no"/>
</LinearLayout>
@@ -95,10 +101,12 @@
style="@style/SettingsLibActionButton.Expressive"
android:layout_width="@dimen/settingslib_expressive_space_large3"
android:layout_height="@dimen/settingslib_expressive_space_medium5"
- android:layout_gravity="center_horizontal" />
+ android:layout_gravity="center_horizontal"
+ android:importantForAccessibility="no"/>
<TextView
android:id="@+id/text4"
style="@style/SettingsLibActionButton.Expressive.Label"
- android:layout_marginTop="@dimen/settingslib_expressive_space_extrasmall3"/>
+ android:layout_marginTop="@dimen/settingslib_expressive_space_extrasmall3"
+ android:importantForAccessibility="no"/>
</LinearLayout>
</LinearLayout>
diff --git a/packages/SettingsLib/ActionButtonsPreference/src/com/android/settingslib/widget/ActionButtonsPreference.java b/packages/SettingsLib/ActionButtonsPreference/src/com/android/settingslib/widget/ActionButtonsPreference.java
index f011039517ce..b2861826a103 100644
--- a/packages/SettingsLib/ActionButtonsPreference/src/com/android/settingslib/widget/ActionButtonsPreference.java
+++ b/packages/SettingsLib/ActionButtonsPreference/src/com/android/settingslib/widget/ActionButtonsPreference.java
@@ -548,16 +548,18 @@ public class ActionButtonsPreference extends Preference {
if (mButton instanceof MaterialButton) {
((MaterialButton) mButton).setIcon(mIcon);
}
+ mButton.setEnabled(mIsEnabled);
+ mActionLayout.setOnClickListener(mListener);
+ mActionLayout.setEnabled(mIsEnabled);
+ mActionLayout.setContentDescription(mText);
} else {
mButton.setText(mText);
mButton.setCompoundDrawablesWithIntrinsicBounds(
null /* left */, mIcon /* top */, null /* right */, null /* bottom */);
+ mButton.setOnClickListener(mListener);
+ mButton.setEnabled(mIsEnabled);
}
- mButton.setOnClickListener(mListener);
- mButton.setEnabled(mIsEnabled);
-
-
if (shouldBeVisible()) {
mButton.setVisibility(View.VISIBLE);
if (mIsExpressive) {
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index d4851e1ad698..e77c2a01b996 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -33,14 +33,17 @@ android_library {
"SettingsLibBarChartPreference",
"SettingsLibButtonPreference",
"SettingsLibBulletPreference",
+ "SettingsLibCardPreference",
"SettingsLibCollapsingToolbarBaseActivity",
"SettingsLibDeviceStateRotationLock",
"SettingsLibDisplayUtils",
"SettingsLibEmergencyNumber",
"SettingsLibEntityHeaderWidgets",
+ "SettingsLibExpandablePreference",
"SettingsLibFooterPreference",
"SettingsLibHelpUtils",
"SettingsLibIllustrationPreference",
+ "SettingsLibIntroPreference",
"SettingsLibLayoutPreference",
"SettingsLibMainSwitchPreference",
"SettingsLibProfileSelector",
diff --git a/packages/SettingsLib/AppPreference/res/layout-v33/preference_app.xml b/packages/SettingsLib/AppPreference/res/layout-v33/preference_app.xml
index 47ce58735048..b06052ad6a00 100644
--- a/packages/SettingsLib/AppPreference/res/layout-v33/preference_app.xml
+++ b/packages/SettingsLib/AppPreference/res/layout-v33/preference_app.xml
@@ -78,15 +78,6 @@
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?android:attr/textColorSecondary"
android:visibility="gone"/>
-
- <ProgressBar
- android:id="@android:id/progress"
- style="?android:attr/progressBarStyleHorizontal"
- android:layout_width="match_parent"
- android:layout_height="4dp"
- android:layout_marginTop="4dp"
- android:max="100"
- android:visibility="gone"/>
</LinearLayout>
<LinearLayout
diff --git a/packages/SettingsLib/AppPreference/res/layout/preference_app.xml b/packages/SettingsLib/AppPreference/res/layout/preference_app.xml
index e65f7de2466a..ac572280c5ad 100644
--- a/packages/SettingsLib/AppPreference/res/layout/preference_app.xml
+++ b/packages/SettingsLib/AppPreference/res/layout/preference_app.xml
@@ -74,15 +74,6 @@
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?android:attr/textColorSecondary"
android:visibility="gone"/>
-
- <ProgressBar
- android:id="@android:id/progress"
- style="?android:attr/progressBarStyleHorizontal"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="4dp"
- android:max="100"
- android:visibility="gone"/>
</LinearLayout>
<LinearLayout
diff --git a/packages/SettingsLib/AppPreference/src/com/android/settingslib/widget/AppPreference.java b/packages/SettingsLib/AppPreference/src/com/android/settingslib/widget/AppPreference.java
index f1d162e116b5..3b52df7e5fbb 100644
--- a/packages/SettingsLib/AppPreference/src/com/android/settingslib/widget/AppPreference.java
+++ b/packages/SettingsLib/AppPreference/src/com/android/settingslib/widget/AppPreference.java
@@ -18,11 +18,8 @@ package com.android.settingslib.widget;
import android.content.Context;
import android.util.AttributeSet;
-import android.view.View;
-import android.widget.ProgressBar;
import androidx.preference.Preference;
-import androidx.preference.PreferenceViewHolder;
import com.android.settingslib.widget.preference.app.R;
@@ -31,9 +28,6 @@ import com.android.settingslib.widget.preference.app.R;
*/
public class AppPreference extends Preference {
- private int mProgress;
- private boolean mProgressVisible;
-
public AppPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
setLayoutResource(R.layout.preference_app);
@@ -53,29 +47,4 @@ public class AppPreference extends Preference {
super(context, attrs);
setLayoutResource(R.layout.preference_app);
}
-
- /**
- * Sets the current progress.
- * @param amount the current progress
- *
- * @see ProgressBar#setProgress(int)
- */
- public void setProgress(int amount) {
- mProgress = amount;
- mProgressVisible = true;
- notifyChanged();
- }
-
- @Override
- public void onBindViewHolder(PreferenceViewHolder view) {
- super.onBindViewHolder(view);
-
- final ProgressBar progress = (ProgressBar) view.findViewById(android.R.id.progress);
- if (mProgressVisible) {
- progress.setProgress(mProgress);
- progress.setVisibility(View.VISIBLE);
- } else {
- progress.setVisibility(View.GONE);
- }
- }
}
diff --git a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled.xml b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled.xml
new file mode 100644
index 000000000000..f55b320269a8
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/settingslib_button"
+ style="@style/SettingsLibButtonStyle.Expressive.Filled" />
+
+</LinearLayout>
diff --git a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled_extra.xml b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled_extra.xml
new file mode 100644
index 000000000000..b663b6ccc5bf
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled_extra.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/settingslib_button"
+ style="@style/SettingsLibButtonStyle.Expressive.Filled.Extra" />
+
+</LinearLayout>
diff --git a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled_large.xml b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled_large.xml
new file mode 100644
index 000000000000..784e6ad6a9f8
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled_large.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/settingslib_button"
+ style="@style/SettingsLibButtonStyle.Expressive.Filled.Large" />
+
+</LinearLayout>
diff --git a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline.xml b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline.xml
new file mode 100644
index 000000000000..8b44a6539801
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/settingslib_button"
+ style="@style/SettingsLibButtonStyle.Expressive.Outline" />
+
+</LinearLayout>
diff --git a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline_extra.xml b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline_extra.xml
new file mode 100644
index 000000000000..f8a2d8fbd975
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline_extra.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/settingslib_button"
+ style="@style/SettingsLibButtonStyle.Expressive.Outline.Extra" />
+
+</LinearLayout>
diff --git a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline_large.xml b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline_large.xml
new file mode 100644
index 000000000000..781a5a136164
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline_large.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/settingslib_button"
+ style="@style/SettingsLibButtonStyle.Expressive.Outline.Large" />
+
+</LinearLayout>
diff --git a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal.xml b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal.xml
new file mode 100644
index 000000000000..5b568f870ea4
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/settingslib_button"
+ style="@style/SettingsLibButtonStyle.Expressive.Tonal" />
+
+</LinearLayout>
diff --git a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal_extra.xml b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal_extra.xml
new file mode 100644
index 000000000000..1e7a08b714f1
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal_extra.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/settingslib_button"
+ style="@style/SettingsLibButtonStyle.Expressive.Tonal.Extra" />
+
+</LinearLayout>
diff --git a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal_large.xml b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal_large.xml
new file mode 100644
index 000000000000..42116be07041
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal_large.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/settingslib_button"
+ style="@style/SettingsLibButtonStyle.Expressive.Tonal.Large" />
+
+</LinearLayout>
diff --git a/packages/SettingsLib/ButtonPreference/res/values-v35/attrs_expressive.xml b/packages/SettingsLib/ButtonPreference/res/values-v35/attrs_expressive.xml
new file mode 100644
index 000000000000..a1761e55f1e0
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/values-v35/attrs_expressive.xml
@@ -0,0 +1,31 @@
+<?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>
+ <declare-styleable name="ButtonPreference">
+ <attr name="buttonType" format="enum">
+ <enum name="filled" value="0"/>
+ <enum name="tonal" value="1"/>
+ <enum name="outline" value="2"/>
+ </attr>
+ <attr name="buttonSize" format="enum">
+ <enum name="normal" value="0"/>
+ <enum name="large" value="1"/>
+ <enum name="extra" value="2"/>
+ </attr>
+ </declare-styleable>
+</resources> \ No newline at end of file
diff --git a/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java b/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java
index 16ba96265751..0041eb2c7072 100644
--- a/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java
+++ b/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java
@@ -32,11 +32,44 @@ import androidx.preference.PreferenceViewHolder;
import com.android.settingslib.widget.preference.button.R;
+import com.google.android.material.button.MaterialButton;
+
/**
* A preference handled a button
*/
public class ButtonPreference extends Preference {
+ enum ButtonStyle {
+ FILLED_NORMAL(0, 0, R.layout.settingslib_expressive_button_filled),
+ FILLED_LARGE(0, 1, R.layout.settingslib_expressive_button_filled_large),
+ FILLED_EXTRA(0, 2, R.layout.settingslib_expressive_button_filled_extra),
+ TONAL_NORMAL(1, 0, R.layout.settingslib_expressive_button_tonal),
+ TONAL_LARGE(1, 1, R.layout.settingslib_expressive_button_tonal_large),
+ TONAL_EXTRA(1, 2, R.layout.settingslib_expressive_button_tonal_extra),
+ OUTLINE_NORMAL(2, 0, R.layout.settingslib_expressive_button_outline),
+ OUTLINE_LARGE(2, 1, R.layout.settingslib_expressive_button_outline_large),
+ OUTLINE_EXTRA(2, 2, R.layout.settingslib_expressive_button_outline_extra);
+
+ private final int mType;
+ private final int mSize;
+ private final int mLayoutId;
+
+ ButtonStyle(int type, int size, int layoutId) {
+ this.mType = type;
+ this.mSize = size;
+ this.mLayoutId = layoutId;
+ }
+
+ static int getLayoutId(int type, int size) {
+ for (ButtonStyle style : values()) {
+ if (style.mType == type && style.mSize == size) {
+ return style.mLayoutId;
+ }
+ }
+ throw new IllegalArgumentException();
+ }
+ }
+
private static final int ICON_SIZE = 24;
private View.OnClickListener mClickListener;
@@ -86,7 +119,7 @@ public class ButtonPreference extends Preference {
}
private void init(Context context, AttributeSet attrs, int defStyleAttr) {
- setLayoutResource(R.layout.settingslib_button_layout);
+ int resId = R.layout.settingslib_button_layout;
if (attrs != null) {
TypedArray a = context.obtainStyledAttributes(attrs,
@@ -102,8 +135,16 @@ public class ButtonPreference extends Preference {
R.styleable.ButtonPreference, defStyleAttr,
0 /*defStyleRes*/);
mGravity = a.getInt(R.styleable.ButtonPreference_android_gravity, Gravity.START);
+
+ if (SettingsThemeHelper.isExpressiveTheme(context)) {
+ int type = a.getInt(R.styleable.ButtonPreference_buttonType, 0);
+ int size = a.getInt(R.styleable.ButtonPreference_buttonSize, 0);
+ resId = ButtonStyle.getLayoutId(type, size);
+ }
a.recycle();
}
+
+ setLayoutResource(resId);
}
@Override
@@ -144,14 +185,20 @@ public class ButtonPreference extends Preference {
if (mButton == null || icon == null) {
return;
}
- //get pixel from dp
- int size = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, ICON_SIZE,
- getContext().getResources().getDisplayMetrics());
- icon.setBounds(0, 0, size, size);
-
- //set drawableStart
- mButton.setCompoundDrawablesRelativeWithIntrinsicBounds(icon, null/* top */, null/* end */,
- null/* bottom */);
+
+ if (mButton instanceof MaterialButton) {
+ ((MaterialButton) mButton).setIcon(icon);
+ } else {
+ //get pixel from dp
+ int size = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, ICON_SIZE,
+ getContext().getResources().getDisplayMetrics());
+ icon.setBounds(0, 0, size, size);
+
+ //set drawableStart
+ mButton.setCompoundDrawablesRelativeWithIntrinsicBounds(icon, null/* top */,
+ null/* end */,
+ null/* bottom */);
+ }
}
@Override
diff --git a/packages/SettingsLib/CardPreference/Android.bp b/packages/SettingsLib/CardPreference/Android.bp
new file mode 100644
index 000000000000..1d871d168ee5
--- /dev/null
+++ b/packages/SettingsLib/CardPreference/Android.bp
@@ -0,0 +1,33 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_library {
+ name: "SettingsLibCardPreference",
+ use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
+
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
+ resource_dirs: ["res"],
+
+ static_libs: [
+ "androidx.annotation_annotation",
+ "androidx.preference_preference",
+ "SettingsLibSettingsTheme",
+ ],
+ sdk_version: "system_current",
+ min_sdk_version: "21",
+ apex_available: [
+ "//apex_available:platform",
+ ],
+}
diff --git a/packages/SettingsLib/CardPreference/AndroidManifest.xml b/packages/SettingsLib/CardPreference/AndroidManifest.xml
new file mode 100644
index 000000000000..717f66e0296c
--- /dev/null
+++ b/packages/SettingsLib/CardPreference/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.settingslib.widget.preference.card">
+
+ <uses-sdk android:minSdkVersion="21" />
+
+</manifest>
diff --git a/packages/SettingsLib/CardPreference/res/layout/settingslib_expressive_preference_card.xml b/packages/SettingsLib/CardPreference/res/layout/settingslib_expressive_preference_card.xml
new file mode 100644
index 000000000000..716ed412eb5c
--- /dev/null
+++ b/packages/SettingsLib/CardPreference/res/layout/settingslib_expressive_preference_card.xml
@@ -0,0 +1,88 @@
+<?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.
+-->
+<com.google.android.material.card.MaterialCardView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ style="@style/SettingsLibCardStyle">
+
+ <LinearLayout
+ android:id="@+id/card_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:baselineAligned="false"
+ android:minHeight="@dimen/settingslib_expressive_space_large3"
+ android:paddingStart="@dimen/settingslib_expressive_space_small1"
+ android:paddingEnd="@dimen/settingslib_expressive_space_small1"
+ android:orientation="horizontal"
+ android:gravity="center_vertical">
+
+ <LinearLayout
+ android:id="@+id/icon_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:minWidth="@dimen/settingslib_expressive_space_medium3"
+ android:minHeight="@dimen/settingslib_expressive_space_medium3"
+ android:gravity="center"
+ android:orientation="horizontal">
+
+ <ImageView
+ android:id="@android:id/icon"
+ android:src="@drawable/settingslib_arrow_drop_down"
+ android:layout_width="@dimen/settingslib_expressive_space_medium3"
+ android:layout_height="@dimen/settingslib_expressive_space_medium3"
+ android:scaleType="centerInside"/>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/text_container"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:paddingHorizontal="@dimen/settingslib_expressive_space_small1"
+ android:paddingVertical="@dimen/settingslib_expressive_space_small2"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@android:id/title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:hyphenationFrequency="normalFast"
+ android:lineBreakWordStyle="phrase"
+ android:textAppearance="@style/TextAppearance.CardTitle.SettingsLib"/>
+
+ <TextView
+ android:id="@android:id/summary"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:hyphenationFrequency="normalFast"
+ android:lineBreakWordStyle="phrase"
+ android:textAppearance="@style/TextAppearance.CardSummary.SettingsLib"/>
+
+ </LinearLayout>
+
+ <ImageView
+ android:id="@android:id/closeButton"
+ android:layout_width="@dimen/settingslib_expressive_space_medium4"
+ android:layout_height="@dimen/settingslib_expressive_space_medium4"
+ android:padding="@dimen/settingslib_expressive_space_extrasmall4"
+ android:layout_gravity="center"
+ android:src="@drawable/settingslib_expressive_icon_close"
+ android:background="?android:attr/selectableItemBackground" />
+
+ </LinearLayout>
+
+</com.google.android.material.card.MaterialCardView> \ No newline at end of file
diff --git a/packages/SettingsLib/CardPreference/res/values/styles_expressive.xml b/packages/SettingsLib/CardPreference/res/values/styles_expressive.xml
new file mode 100644
index 000000000000..4cbdea52d439
--- /dev/null
+++ b/packages/SettingsLib/CardPreference/res/values/styles_expressive.xml
@@ -0,0 +1,30 @@
+<?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>
+ <style name="TextAppearance.CardTitle.SettingsLib"
+ parent="@style/TextAppearance.PreferenceTitle.SettingsLib">
+ <item name="android:textColor">@color/settingslib_materialColorOnPrimary</item>
+ <item name="android:textSize">20sp</item>
+ </style>
+
+ <style name="TextAppearance.CardSummary.SettingsLib"
+ parent="@style/TextAppearance.PreferenceSummary.SettingsLib">
+ <item name="android:textColor">@color/settingslib_materialColorOnSecondary</item>
+ <item name="android:textSize">14sp</item>
+ </style>
+</resources> \ No newline at end of file
diff --git a/packages/SettingsLib/CardPreference/src/com/android/settingslib/widget/CardPreference.kt b/packages/SettingsLib/CardPreference/src/com/android/settingslib/widget/CardPreference.kt
new file mode 100644
index 000000000000..eb14746a0f22
--- /dev/null
+++ b/packages/SettingsLib/CardPreference/src/com/android/settingslib/widget/CardPreference.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settingslib.widget
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import androidx.preference.Preference
+import androidx.preference.PreferenceViewHolder
+import com.android.settingslib.widget.preference.card.R
+
+/**
+ * The CardPreference shows a card like suggestion in homepage, which also support dismiss.
+ */
+class CardPreference @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0,
+ defStyleRes: Int = 0
+) : Preference(context, attrs, defStyleAttr, defStyleRes) {
+
+ init {
+ layoutResource = R.layout.settingslib_expressive_preference_card
+ }
+ private var dismissible = false
+ set(value) {
+ if (field != value) {
+ field = value
+ notifyChanged()
+ }
+ }
+
+ override fun onBindViewHolder(holder: PreferenceViewHolder) {
+ super.onBindViewHolder(holder)
+ holder.isDividerAllowedBelow = false
+ holder.isDividerAllowedAbove = false
+
+ holder.findViewById(android.R.id.closeButton)?.let { dismissButton ->
+ dismissButton.visibility = if (dismissible) View.VISIBLE else View.GONE
+ dismissButton.setOnClickListener {
+ isVisible = false
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/packages/SettingsLib/ExpandablePreference/Android.bp b/packages/SettingsLib/ExpandablePreference/Android.bp
new file mode 100644
index 000000000000..e6de3f196cd5
--- /dev/null
+++ b/packages/SettingsLib/ExpandablePreference/Android.bp
@@ -0,0 +1,33 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_library {
+ name: "SettingsLibExpandablePreference",
+ use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
+
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
+ resource_dirs: ["res"],
+
+ static_libs: [
+ "androidx.annotation_annotation",
+ "androidx.preference_preference",
+ "SettingsLibSettingsTheme",
+ ],
+ sdk_version: "system_current",
+ min_sdk_version: "21",
+ apex_available: [
+ "//apex_available:platform",
+ ],
+}
diff --git a/packages/SettingsLib/ExpandablePreference/AndroidManifest.xml b/packages/SettingsLib/ExpandablePreference/AndroidManifest.xml
new file mode 100644
index 000000000000..7bfa19b1009c
--- /dev/null
+++ b/packages/SettingsLib/ExpandablePreference/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?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");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.settingslib.widget.preference.expandable">
+
+ <uses-sdk android:minSdkVersion="21" />
+
+</manifest>
diff --git a/packages/SettingsLib/ExpandablePreference/res/drawable/settingslib_ic_expand.xml b/packages/SettingsLib/ExpandablePreference/res/drawable/settingslib_ic_expand.xml
new file mode 100644
index 000000000000..450231108b7a
--- /dev/null
+++ b/packages/SettingsLib/ExpandablePreference/res/drawable/settingslib_ic_expand.xml
@@ -0,0 +1,37 @@
+<?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.
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item>
+ <shape
+ android:shape="oval">
+ <size android:width="32dp" android:height="40dp"/>
+ <solid android:color="@color/settingslib_materialColorSurfaceContainer"/>
+ </shape>
+ </item>
+ <item>
+ <vector
+ android:width="32dp"
+ android:height="40dp"
+ android:viewportWidth="32"
+ android:viewportHeight="40">
+ <path
+ android:pathData="M16,23.063L11,18.063L12.063,17L16,20.938L19.938,17L21,18.063L16,23.063Z"
+ android:fillColor="@color/settingslib_materialColorOnSurface"/>
+ </vector>
+ </item>
+</layer-list> \ No newline at end of file
diff --git a/packages/SettingsLib/ExpandablePreference/res/layout/settingslib_widget_expandable_icon.xml b/packages/SettingsLib/ExpandablePreference/res/layout/settingslib_widget_expandable_icon.xml
new file mode 100644
index 000000000000..f7fefef36b27
--- /dev/null
+++ b/packages/SettingsLib/ExpandablePreference/res/layout/settingslib_widget_expandable_icon.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <ImageView
+ android:id="@+id/expand_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/settingslib_ic_expand"/>
+</LinearLayout> \ No newline at end of file
diff --git a/packages/SettingsLib/ExpandablePreference/src/com/android/settingslib/widget/ExpandablePreference.kt b/packages/SettingsLib/ExpandablePreference/src/com/android/settingslib/widget/ExpandablePreference.kt
new file mode 100644
index 000000000000..1b93ebecb3be
--- /dev/null
+++ b/packages/SettingsLib/ExpandablePreference/src/com/android/settingslib/widget/ExpandablePreference.kt
@@ -0,0 +1,95 @@
+/*
+ * 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.widget
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.ImageView
+import androidx.preference.Preference
+import androidx.preference.PreferenceGroup
+import androidx.preference.PreferenceViewHolder
+import com.android.settingslib.widget.preference.expandable.R
+
+class ExpandablePreference @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0,
+ defStyleRes: Int = 0
+) : PreferenceGroup(context, attrs, defStyleAttr, defStyleRes), Expandable {
+
+ private var isExpanded = false
+ private var expandIcon: ImageView? = null
+ private var isDirty = true // Flag to track changes
+
+ init {
+ layoutResource = com.android.settingslib.widget.theme.R.layout.settingslib_expressive_preference
+ widgetLayoutResource = R.layout.settingslib_widget_expandable_icon
+ }
+
+ override fun onBindViewHolder(holder: PreferenceViewHolder) {
+ super.onBindViewHolder(holder)
+
+ holder.isDividerAllowedAbove = false
+ holder.isDividerAllowedBelow = false
+
+ expandIcon = holder.findViewById(R.id.expand_icon) as ImageView?
+
+ updateExpandedState()
+
+ holder.itemView.setOnClickListener { toggleExpansion() }
+ }
+
+ override fun addPreference(preference: Preference): Boolean {
+ preference.isVisible = isExpanded
+ return super.addPreference(preference)
+ }
+
+ override fun onPrepareAddPreference(preference: Preference): Boolean {
+ preference.isVisible = isExpanded
+ return super.onPrepareAddPreference(preference)
+ }
+
+ override fun isExpanded(): Boolean {
+ return isExpanded
+ }
+
+ private fun toggleExpansion() {
+ isExpanded = !isExpanded
+ isDirty = true // Mark as dirty when expansion state changes
+ updateExpandedState()
+ notifyChanged()
+ }
+
+ private fun updateExpandedState() {
+ expandIcon?.rotation = when (isExpanded) {
+ true -> ROTATION_EXPANDED
+ false -> ROTATION_COLLAPSED
+ }
+
+ if (isDirty) {
+ (0 until preferenceCount).forEach { i ->
+ getPreference(i).isVisible = isExpanded
+ }
+ isDirty = false
+ }
+ }
+
+ companion object {
+ private const val ROTATION_EXPANDED = 180f
+ private const val ROTATION_COLLAPSED = 0f
+ }
+} \ No newline at end of file
diff --git a/packages/SettingsLib/IntroPreference/Android.bp b/packages/SettingsLib/IntroPreference/Android.bp
new file mode 100644
index 000000000000..155db186c702
--- /dev/null
+++ b/packages/SettingsLib/IntroPreference/Android.bp
@@ -0,0 +1,33 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_library {
+ name: "SettingsLibIntroPreference",
+ use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
+
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
+ resource_dirs: ["res"],
+
+ static_libs: [
+ "androidx.preference_preference",
+ "SettingsLibSettingsTheme",
+ ],
+
+ sdk_version: "system_current",
+ min_sdk_version: "21",
+ apex_available: [
+ "//apex_available:platform",
+ ],
+}
diff --git a/packages/SettingsLib/IntroPreference/AndroidManifest.xml b/packages/SettingsLib/IntroPreference/AndroidManifest.xml
new file mode 100644
index 000000000000..f1bfee5524e7
--- /dev/null
+++ b/packages/SettingsLib/IntroPreference/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.settingslib.widget.preference.intro">
+
+ <uses-sdk android:minSdkVersion="21" />
+
+</manifest>
diff --git a/packages/SettingsLib/IntroPreference/res/layout/settingslib_expressive_preference_intro.xml b/packages/SettingsLib/IntroPreference/res/layout/settingslib_expressive_preference_intro.xml
new file mode 100644
index 000000000000..203a395c3e98
--- /dev/null
+++ b/packages/SettingsLib/IntroPreference/res/layout/settingslib_expressive_preference_intro.xml
@@ -0,0 +1,45 @@
+<?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.
+ -->
+
+<RelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/entity_header"
+ style="@style/SettingsLibEntityHeader">
+
+ <LinearLayout
+ android:id="@+id/entity_header_content"
+ style="@style/SettingsLibEntityHeaderContent">
+
+ <ImageView
+ android:id="@android:id/icon"
+ android:src="@drawable/settingslib_arrow_drop_down"
+ style="@style/SettingsLibEntityHeaderIcon"/>
+
+ <TextView
+ android:id="@android:id/title"
+ android:text="Title"
+ style="@style/SettingsLibEntityHeaderTitle"/>
+
+ <com.android.settingslib.widget.CollapsableTextView
+ android:id="@+id/collapsable_summary"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center"/>
+
+ </LinearLayout>
+
+</RelativeLayout>
diff --git a/packages/SettingsLib/IntroPreference/src/com/android/settingslib/widget/IntroPreference.kt b/packages/SettingsLib/IntroPreference/src/com/android/settingslib/widget/IntroPreference.kt
new file mode 100644
index 000000000000..c93ec2bd0492
--- /dev/null
+++ b/packages/SettingsLib/IntroPreference/src/com/android/settingslib/widget/IntroPreference.kt
@@ -0,0 +1,102 @@
+/*
+ * 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.widget
+
+import android.content.Context
+import android.os.Build
+import android.util.AttributeSet
+import androidx.annotation.RequiresApi
+import androidx.preference.Preference
+import androidx.preference.PreferenceViewHolder
+import com.android.settingslib.widget.preference.intro.R
+
+class IntroPreference @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0,
+ defStyleRes: Int = 0
+) : Preference(context, attrs, defStyleAttr, defStyleRes) {
+
+ private var isCollapsable: Boolean = false
+ private var minLines: Int = 2
+
+ init {
+ layoutResource = R.layout.settingslib_expressive_preference_intro
+ isSelectable = false
+
+ initAttributes(context, attrs, defStyleAttr)
+ }
+
+ private fun initAttributes(context: Context, attrs: AttributeSet?, defStyleAttr: Int) {
+ context.obtainStyledAttributes(
+ attrs,
+ COLLAPSABLE_TEXT_VIEW_ATTRS, defStyleAttr, 0
+ ).apply {
+ isCollapsable = getBoolean(IS_COLLAPSABLE, false)
+ minLines = getInt(
+ MIN_LINES,
+ if (isCollapsable) DEFAULT_MIN_LINES else DEFAULT_MAX_LINES
+ ).coerceIn(1, DEFAULT_MAX_LINES)
+ recycle()
+ }
+ }
+
+ override fun onBindViewHolder(holder: PreferenceViewHolder) {
+ super.onBindViewHolder(holder)
+ holder.isDividerAllowedBelow = false
+ holder.isDividerAllowedAbove = false
+
+ (holder.findViewById(R.id.collapsable_summary) as? CollapsableTextView)?.apply {
+ setCollapsable(isCollapsable)
+ setMinLines(minLines)
+ setText(summary.toString())
+ }
+ }
+
+ /**
+ * Sets whether the summary is collapsable.
+ * @param collapsable True if the summary should be collapsable, false otherwise.
+ */
+ @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ fun setCollapsable(collapsable: Boolean) {
+ isCollapsable = collapsable
+ minLines = if (isCollapsable) DEFAULT_MIN_LINES else DEFAULT_MAX_LINES
+ notifyChanged()
+ }
+
+ /**
+ * Sets the minimum number of lines to display when collapsed.
+ * @param lines The minimum number of lines.
+ */
+ @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ fun setMinLines(lines: Int) {
+ minLines = lines.coerceIn(1, DEFAULT_MAX_LINES)
+ notifyChanged()
+ }
+
+ companion object {
+ private const val DEFAULT_MAX_LINES = 10
+ private const val DEFAULT_MIN_LINES = 2
+
+ private val COLLAPSABLE_TEXT_VIEW_ATTRS =
+ com.android.settingslib.widget.theme.R.styleable.CollapsableTextView
+ private val MIN_LINES =
+ com.android.settingslib.widget.theme.R.styleable.CollapsableTextView_android_minLines
+ private val IS_COLLAPSABLE =
+ com.android.settingslib.widget.theme.R.styleable.CollapsableTextView_isCollapsable
+ }
+} \ No newline at end of file
diff --git a/packages/SettingsLib/Preference/Android.bp b/packages/SettingsLib/Preference/Android.bp
index 17852e8e7ece..e83e17cc8375 100644
--- a/packages/SettingsLib/Preference/Android.bp
+++ b/packages/SettingsLib/Preference/Android.bp
@@ -22,3 +22,16 @@ android_library {
],
kotlincflags: ["-Xjvm-default=all"],
}
+
+android_library {
+ name: "SettingsLibPreference-testutils",
+ srcs: ["testutils/**/*.kt"],
+ static_libs: [
+ "SettingsLibPreference",
+ "androidx.fragment_fragment-testing",
+ "androidx.test.core",
+ "androidx.test.ext.junit",
+ "flag-junit",
+ "truth",
+ ],
+}
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt
index 5fcf4784f43b..5e6989546cb9 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt
@@ -68,10 +68,13 @@ interface PreferenceBinding {
preference.icon = null
}
val context = preference.context
+ val isPreferenceScreen = preference is PreferenceScreen
preference.peekExtras()?.clear()
extras(context)?.let { preference.extras.putAll(it) }
preference.title = getPreferenceTitle(context)
- preference.summary = getPreferenceSummary(context)
+ if (!isPreferenceScreen) {
+ preference.summary = getPreferenceSummary(context)
+ }
preference.isEnabled = isEnabled(context)
preference.isVisible =
(this as? PreferenceAvailabilityProvider)?.isAvailable(context) != false
@@ -81,7 +84,7 @@ interface PreferenceBinding {
// dependency here. This simplifies dependency management and avoid the
// IllegalStateException when call Preference.setDependency
preference.dependency = null
- if (preference !is PreferenceScreen) { // avoid recursive loop when build graph
+ if (!isPreferenceScreen) { // avoid recursive loop when build graph
preference.fragment = (this as? PreferenceScreenCreator)?.fragmentClass()?.name
preference.intent = intent(context)
}
diff --git a/packages/SettingsLib/Preference/testutils/com/android/settingslib/preference/CatalystScreenTestCase.kt b/packages/SettingsLib/Preference/testutils/com/android/settingslib/preference/CatalystScreenTestCase.kt
new file mode 100644
index 000000000000..4d5f85fa9020
--- /dev/null
+++ b/packages/SettingsLib/Preference/testutils/com/android/settingslib/preference/CatalystScreenTestCase.kt
@@ -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.
+ */
+
+package com.android.settingslib.preference
+
+import android.content.Context
+import android.platform.test.flag.junit.SetFlagsRule
+import android.util.Log
+import androidx.fragment.app.testing.FragmentScenario
+import androidx.preference.Preference
+import androidx.preference.PreferenceFragmentCompat
+import androidx.preference.PreferenceGroup
+import androidx.preference.PreferenceScreen
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/** Test case for catalyst screen. */
+@RunWith(AndroidJUnit4::class)
+abstract class CatalystScreenTestCase {
+ @get:Rule val setFlagsRule = SetFlagsRule()
+
+ protected val context: Context = ApplicationProvider.getApplicationContext()
+
+ /** Catalyst screen. */
+ protected abstract val preferenceScreenCreator: PreferenceScreenCreator
+
+ /** Flag to control catalyst screen. */
+ protected abstract val flagName: String
+
+ /**
+ * Test to compare the preference screen hierarchy between legacy screen (flag is disabled) and
+ * catalyst screen (flag is enabled).
+ */
+ @Test
+ fun migration() {
+ enableCatalystScreen()
+ assertThat(preferenceScreenCreator.isFlagEnabled(context)).isTrue()
+ val catalystScreen = stringifyPreferenceScreen()
+ Log.i("Catalyst", catalystScreen)
+
+ disableCatalystScreen()
+ assertThat(preferenceScreenCreator.isFlagEnabled(context)).isFalse()
+ val legacyScreen = stringifyPreferenceScreen()
+
+ assertThat(catalystScreen).isEqualTo(legacyScreen)
+ }
+
+ /**
+ * Enables the catalyst screen.
+ *
+ * By default, enable the [flagName]. Override for more complex situation.
+ */
+ @Suppress("DEPRECATION")
+ protected open fun enableCatalystScreen() {
+ setFlagsRule.enableFlags(flagName)
+ }
+
+ /**
+ * Disables the catalyst screen (legacy screen is shown).
+ *
+ * By default, disable the [flagName]. Override for more complex situation.
+ */
+ @Suppress("DEPRECATION")
+ protected open fun disableCatalystScreen() {
+ setFlagsRule.disableFlags(flagName)
+ }
+
+ private fun stringifyPreferenceScreen(): String {
+ @Suppress("UNCHECKED_CAST")
+ val clazz = preferenceScreenCreator.fragmentClass() as Class<PreferenceFragmentCompat>
+ val builder = StringBuilder()
+ FragmentScenario.launch(clazz).use {
+ it.onFragment { fragment -> fragment.preferenceScreen.toString(builder) }
+ }
+ return builder.toString()
+ }
+
+ private fun Preference.toString(builder: StringBuilder, indent: String = "") {
+ val clazz = javaClass
+ builder.append(indent).append(clazz).append(" {\n")
+ val indent2 = "$indent "
+ if (clazz != PreferenceScreen::class.java) {
+ key?.let { builder.append(indent2).append("key: \"$it\"\n") }
+ }
+ title?.let { builder.append(indent2).append("title: \"$it\"\n") }
+ summary?.let { builder.append(indent2).append("summary: \"$it\"\n") }
+ fragment?.let { builder.append(indent2).append("fragment: \"$it\"\n") }
+ builder.append(indent2).append("order: $order\n")
+ builder.append(indent2).append("isCopyingEnabled: $isCopyingEnabled\n")
+ builder.append(indent2).append("isEnabled: $isEnabled\n")
+ builder.append(indent2).append("isIconSpaceReserved: $isIconSpaceReserved\n")
+ if (clazz != Preference::class.java && clazz != PreferenceScreen::class.java) {
+ builder.append(indent2).append("isPersistent: $isPersistent\n")
+ }
+ builder.append(indent2).append("isSelectable: $isSelectable\n")
+ if (this is PreferenceGroup) {
+ val count = preferenceCount
+ builder.append(indent2).append("preferenceCount: $count\n")
+ val indent4 = "$indent2 "
+ for (index in 0..<count) {
+ getPreference(index).toString(builder, indent4)
+ }
+ }
+ builder.append(indent).append("}\n")
+ }
+}
diff --git a/packages/SettingsLib/SearchWidget/res/values-ms/strings.xml b/packages/SettingsLib/SearchWidget/res/values-ms/strings.xml
index 99a9b05b9a62..13a7b5c951db 100644
--- a/packages/SettingsLib/SearchWidget/res/values-ms/strings.xml
+++ b/packages/SettingsLib/SearchWidget/res/values-ms/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="search_menu" msgid="1914043873178389845">"Cari tetapan"</string>
+ <string name="search_menu" msgid="1914043873178389845">"Cari dalam tetapan"</string>
</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_collapse.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_collapse.xml
new file mode 100644
index 000000000000..161ece73f21c
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_collapse.xml
@@ -0,0 +1,36 @@
+<?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.
+ -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item>
+ <shape
+ android:shape="oval">
+ <size android:width="24dp" android:height="24dp"/>
+ <solid android:color="@color/settingslib_materialColorSurfaceDim"/>
+ </shape>
+ </item>
+ <item>
+ <vector
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960">
+ <path
+ android:fillColor="@color/settingslib_materialColorOnSurface"
+ android:pathData="M480,432L296,616L240,560L480,320L720,560L664,616L480,432Z"/>
+ </vector>
+ </item>
+</layer-list> \ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_expand.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_expand.xml
new file mode 100644
index 000000000000..1b5d5182d9b2
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_expand.xml
@@ -0,0 +1,37 @@
+<?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.
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item>
+ <shape
+ android:shape="oval">
+ <size android:width="24dp" android:height="24dp"/>
+ <solid android:color="@color/settingslib_materialColorSurfaceDim"/>
+ </shape>
+ </item>
+ <item>
+ <vector
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960">
+ <path
+ android:fillColor="@color/settingslib_materialColorOnSurface"
+ android:pathData="M480,616L240,376L296,320L480,504L664,320L720,376L480,616Z"/>
+ </vector>
+ </item>
+</layer-list> \ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_collapsable_textview.xml b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_collapsable_textview.xml
new file mode 100644
index 000000000000..4cc3c89dadb5
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_collapsable_textview.xml
@@ -0,0 +1,62 @@
+<?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.
+ -->
+
+<androidx.constraintlayout.widget.ConstraintLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingBottom="@dimen/settingslib_expressive_space_small1"
+ android:paddingTop="@dimen/settingslib_expressive_space_extrasmall4"
+ android:orientation="vertical"
+ android:animateLayoutChanges="true"
+ android:background="?android:attr/selectableItemBackground"
+ android:clipToPadding="false">
+
+ <TextView
+ android:id="@android:id/title"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ android:textAlignment="viewStart"
+ android:clickable="false"
+ android:longClickable="false"
+ android:maxLines="10"
+ android:ellipsize="end"
+ android:textAppearance="@style/TextAppearance.TopIntroText"/>
+
+ <com.android.settingslib.widget.LinkableTextView
+ android:id="@+id/settingslib_expressive_learn_more"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ app:layout_constraintTop_toBottomOf="@android:id/title"
+ app:layout_constraintStart_toStartOf="parent"
+ android:textAlignment="viewStart"
+ android:clickable="true"
+ android:visibility="gone"
+ style="@style/SettingslibTextAppearance.LinkableTextStyle.Expressive"/>
+
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/collapse_button"
+ app:layout_constraintTop_toBottomOf="@id/settingslib_expressive_learn_more"
+ app:layout_constraintStart_toStartOf="parent"
+ android:text="@string/settingslib_expressive_text_expand"
+ app:icon="@drawable/settingslib_expressive_icon_expand"
+ style="@style/SettingslibTextButtonStyle.Expressive"/>
+</androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/attrs_expressive.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/attrs_expressive.xml
new file mode 100644
index 000000000000..857dd7953234
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-v35/attrs_expressive.xml
@@ -0,0 +1,25 @@
+<?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>
+ <declare-styleable name="CollapsableTextView">
+ <attr name="android:gravity"/>
+ <!-- The minimum number of lines when the textView collapsed. -->
+ <attr name="android:minLines"/>
+ <!-- Specifies that the textView is collapsable. -->
+ <attr name="isCollapsable" format="boolean"/>
+ </declare-styleable>
+</resources> \ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/strings.xml
new file mode 100644
index 000000000000..22734068733a
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-v35/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+ <!-- text of button to indicate user the textView is expandable [CHAR LIMIT=NONE] -->
+ <string name="settingslib_expressive_text_expand">Expand</string>
+ <!-- text of button to indicate user the textView is collapsable [CHAR LIMIT=NONE] -->
+ <string name="settingslib_expressive_text_collapse">Collapse</string>
+</resources> \ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/styles_expressive.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/styles_expressive.xml
index 816433c1a18b..88f7eb8737a2 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v35/styles_expressive.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v35/styles_expressive.xml
@@ -170,6 +170,52 @@
<item name="thumbIcon">@drawable/settingslib_expressive_switch_thumb_icon</item>
</style>
+ <style name="SettingslibMainSwitchStyle.Expressive" parent="SettingslibSwitchStyle.Expressive">
+ <item name="android:layout_gravity">center</item>
+ <item name="trackTint">@color/settingslib_expressive_color_main_switch_track</item>
+ </style>
+
+ <style name="SettingsLibCardStyle" parent="">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:layout_marginHorizontal">?android:attr/listPreferredItemPaddingStart</item>
+ <item name="android:layout_marginVertical">@dimen/settingslib_expressive_space_extrasmall4</item>
+ <item name="cardBackgroundColor">@color/settingslib_materialColorPrimary</item>
+ <item name="cardCornerRadius">@dimen/settingslib_expressive_radius_extralarge3</item>
+ <item name="cardElevation">0dp</item>
+ <item name="rippleColor">?android:attr/colorControlHighlight</item>
+ </style>
+
+ <style name="SettingsLibButtonStyle.Expressive.Filled"
+ parent="@style/Widget.Material3.Button">
+ <item name="android:theme">@style/Theme.Material3.DynamicColors.DayNight</item>
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:gravity">center</item>
+ <item name="android:minWidth">@dimen/settingslib_expressive_space_medium4</item>
+ <item name="android:minHeight">@dimen/settingslib_expressive_space_medium4</item>
+ <item name="android:paddingVertical">@dimen/settingslib_expressive_space_extrasmall5</item>
+ <item name="android:paddingHorizontal">@dimen/settingslib_expressive_space_small1</item>
+ <item name="android:backgroundTint">@color/settingslib_materialColorPrimary</item>
+ <item name="android:textAppearance">@android:style/TextAppearance.DeviceDefault.Medium</item>
+ <item name="android:textColor">@color/settingslib_materialColorOnPrimary</item>
+ <item name="android:textSize">14sp</item>
+ <item name="iconGravity">textStart</item>
+ <item name="iconTint">@color/settingslib_materialColorOnPrimary</item>
+ <item name="iconSize">@dimen/settingslib_expressive_space_small4</item>
+ </style>
+
+ <style name="SettingsLibButtonStyle.Expressive.Filled.Large">
+ <item name="android:paddingVertical">@dimen/settingslib_expressive_space_small1</item>
+ <item name="android:paddingHorizontal">@dimen/settingslib_expressive_space_small4</item>
+ <item name="android:textSize">16sp</item>
+ </style>
+
+ <style name="SettingsLibButtonStyle.Expressive.Filled.Extra"
+ parent="@style/SettingsLibButtonStyle.Expressive.Filled.Large">
+ <item name="android:layout_width">match_parent</item>
+ </style>
+
<style name="SettingsLibButtonStyle.Expressive.Tonal"
parent="@style/Widget.Material3.Button.TonalButton">
<item name="android:theme">@style/Theme.Material3.DynamicColors.DayNight</item>
@@ -189,8 +235,104 @@
<item name="iconSize">@dimen/settingslib_expressive_space_small4</item>
</style>
- <style name="SettingslibMainSwitchStyle.Expressive" parent="SettingslibSwitchStyle.Expressive">
- <item name="android:layout_gravity">center</item>
- <item name="trackTint">@color/settingslib_expressive_color_main_switch_track</item>
+ <style name="SettingsLibButtonStyle.Expressive.Tonal.Large">
+ <item name="android:paddingVertical">@dimen/settingslib_expressive_space_small1</item>
+ <item name="android:paddingHorizontal">@dimen/settingslib_expressive_space_small4</item>
+ <item name="android:textSize">16sp</item>
+ </style>
+
+ <style name="SettingsLibButtonStyle.Expressive.Tonal.Extra"
+ parent="@style/SettingsLibButtonStyle.Expressive.Tonal.Large">
+ <item name="android:layout_width">match_parent</item>
+ </style>
+
+ <style name="SettingsLibButtonStyle.Expressive.Outline"
+ parent="@style/Widget.Material3.Button.OutlinedButton.Icon">
+ <item name="android:theme">@style/Theme.Material3.DynamicColors.DayNight</item>
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:gravity">center</item>
+ <item name="android:minWidth">@dimen/settingslib_expressive_space_medium4</item>
+ <item name="android:minHeight">@dimen/settingslib_expressive_space_medium4</item>
+ <item name="android:paddingVertical">@dimen/settingslib_expressive_space_extrasmall5</item>
+ <item name="android:paddingHorizontal">@dimen/settingslib_expressive_space_small1</item>
+ <item name="android:textAppearance">@android:style/TextAppearance.DeviceDefault.Medium</item>
+ <item name="android:textColor">@color/settingslib_materialColorPrimary</item>
+ <item name="android:textSize">14sp</item>
+ <item name="iconTint">@color/settingslib_materialColorPrimary</item>
+ <item name="iconGravity">textStart</item>
+ <item name="iconSize">@dimen/settingslib_expressive_space_small4</item>
+ <item name="iconPadding">@dimen/settingslib_expressive_space_extrasmall4</item>
+ <item name="strokeColor">@color/settingslib_materialColorOutlineVariant</item>
+
+ </style>
+
+ <style name="SettingsLibButtonStyle.Expressive.Outline.Large">
+ <item name="android:paddingVertical">@dimen/settingslib_expressive_space_small1</item>
+ <item name="android:paddingHorizontal">@dimen/settingslib_expressive_space_small4</item>
+ <item name="android:textSize">16sp</item>
+ </style>
+
+ <style name="SettingsLibButtonStyle.Expressive.Outline.Extra"
+ parent="@style/SettingsLibButtonStyle.Expressive.Outline.Large">
+ <item name="android:layout_width">match_parent</item>
+ </style>
+
+ <style name="SettingslibTextButtonStyle.Expressive"
+ parent="@style/Widget.Material3.Button.TextButton.Icon">
+ <item name="android:theme">@style/Theme.Material3.DynamicColors.DayNight</item>
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:textAppearance">@android:style/TextAppearance.DeviceDefault.Medium</item>
+ <item name="android:textSize">16sp</item>
+ <item name="android:textColor">@color/settingslib_materialColorOnSurface</item>
+ <item name="iconTint">@null</item>
+ <item name="iconPadding">@dimen/settingslib_expressive_space_extrasmall4</item>
+ <item name="rippleColor">?android:attr/colorControlHighlight</item>
+ </style>
+
+ <style name="EntityHeader">
+ <item name="android:paddingTop">@dimen/settingslib_expressive_space_small4</item>
+ <item name="android:paddingBottom">@dimen/settingslib_expressive_space_small1</item>
+ <item name="android:paddingEnd">@dimen/settingslib_expressive_space_small1</item>
+ </style>
+
+ <style name="SettingsLibEntityHeader" parent="EntityHeader">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:paddingStart">?android:attr/listPreferredItemPaddingStart</item>
+ <item name="android:paddingEnd">?android:attr/listPreferredItemPaddingEnd</item>
+ </style>
+
+ <style name="SettingsLibEntityHeaderContent">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:layout_centerHorizontal">true</item>
+ <item name="android:orientation">vertical</item>
+ <item name="android:gravity">center_horizontal</item>
+ </style>
+
+ <style name="SettingsLibEntityHeaderIcon">
+ <item name="android:layout_width">@dimen/settingslib_expressive_space_large3</item>
+ <item name="android:layout_height">@dimen/settingslib_expressive_space_large3</item>
+ <item name="android:scaleType">fitCenter</item>
+ <item name="android:antialias">true</item>
+ </style>
+
+ <style name="SettingsLibEntityHeaderTitle">
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:layout_marginTop">@dimen/settingslib_expressive_space_small1</item>
+ <item name="android:singleLine">false</item>
+ <item name="android:gravity">center</item>
+ <item name="android:ellipsize">marquee</item>
+ <item name="android:textDirection">locale</item>
+ <item name="android:textAppearance">@style/TextAppearance.EntityHeaderTitle</item>
+ </style>
+
+ <style name="SettingslibTextAppearance.LinkableTextStyle.Expressive"
+ parent="@android:style/TextAppearance.DeviceDefault.WindowTitle">
+ <item name="android:textSize">14sp</item>
+ <item name="android:textColor">?android:attr/colorAccent</item>
</style>
</resources> \ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/CollapsableTextView.kt b/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/CollapsableTextView.kt
new file mode 100644
index 000000000000..7797a0d6dcde
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/CollapsableTextView.kt
@@ -0,0 +1,208 @@
+/*
+ * 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.widget
+
+import android.content.Context
+import android.graphics.drawable.Drawable
+import android.text.SpannableString
+import android.text.TextUtils
+import android.text.style.URLSpan
+import android.util.AttributeSet
+import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.View
+import android.widget.TextView
+import androidx.constraintlayout.widget.ConstraintLayout
+import com.android.settingslib.widget.theme.R
+import com.google.android.material.button.MaterialButton
+
+class CollapsableTextView @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0
+) : ConstraintLayout(context, attrs, defStyleAttr) {
+
+ private var isCollapsable: Boolean = false
+ private var isCollapsed: Boolean = false
+ private var minLines: Int = DEFAULT_MIN_LINES
+
+ private val titleTextView: TextView
+ private val collapseButton: MaterialButton
+ private val collapseButtonResources: CollapseButtonResources
+ private var learnMoreListener: View.OnClickListener? = null
+ private var learnMoreText: CharSequence? = null
+ private var learnMoreSpan: LearnMoreSpan? = null
+ val learnMoreTextView: LinkableTextView
+ var isLearnMoreEnabled: Boolean = false
+
+ init {
+ LayoutInflater.from(context)
+ .inflate(R.layout.settingslib_expressive_collapsable_textview, this)
+ titleTextView = findViewById(android.R.id.title)
+ collapseButton = findViewById(R.id.collapse_button)
+ learnMoreTextView = findViewById(R.id.settingslib_expressive_learn_more)
+
+ collapseButtonResources = CollapseButtonResources(
+ context.getDrawable(R.drawable.settingslib_expressive_icon_collapse)!!,
+ context.getDrawable(R.drawable.settingslib_expressive_icon_expand)!!,
+ context.getString(R.string.settingslib_expressive_text_collapse),
+ context.getString(R.string.settingslib_expressive_text_expand)
+ )
+
+ collapseButton.setOnClickListener {
+ isCollapsed = !isCollapsed
+ updateView()
+ }
+
+ initAttributes(context, attrs, defStyleAttr)
+ }
+
+ private fun initAttributes(context: Context, attrs: AttributeSet?, defStyleAttr: Int) {
+ context.obtainStyledAttributes(
+ attrs, Attrs, defStyleAttr, 0
+ ).apply {
+ val gravity = getInt(GravityAttr, Gravity.START)
+ when (gravity) {
+ Gravity.CENTER_VERTICAL, Gravity.CENTER, Gravity.CENTER_HORIZONTAL -> {
+ centerHorizontally(titleTextView)
+ centerHorizontally(collapseButton)
+ }
+ }
+ recycle()
+ }
+ }
+
+ private fun centerHorizontally(view: View) {
+ (view.layoutParams as LayoutParams).apply {
+ startToStart = LayoutParams.PARENT_ID
+ endToEnd = LayoutParams.PARENT_ID
+ horizontalBias = 0.5f
+ }
+ }
+
+ /**
+ * Sets the text content of the CollapsableTextView.
+ * @param text The text to display.
+ */
+ fun setText(text: String) {
+ titleTextView.text = text
+ }
+
+ /**
+ * Sets whether the text view is collapsable.
+ * @param collapsable True if the text view should be collapsable, false otherwise.
+ */
+ fun setCollapsable(collapsable: Boolean) {
+ isCollapsable = collapsable
+ updateView()
+ }
+
+ /**
+ * Sets the minimum number of lines to display when collapsed.
+ * @param lines The minimum number of lines.
+ */
+ fun setMinLines(line: Int) {
+ minLines = line.coerceIn(1, DEFAULT_MAX_LINES)
+ updateView()
+ }
+
+ /**
+ * Sets the action when clicking on the learn more view.
+ * @param listener The click listener for learn more.
+ */
+ fun setLearnMoreAction(listener: View.OnClickListener?) {
+ if (learnMoreListener != listener) {
+ learnMoreListener = listener
+ formatLearnMoreText()
+ }
+ }
+
+ /**
+ * Sets the text of learn more view.
+ * @param text The text of learn more.
+ */
+ fun setLearnMoreText(text: CharSequence?) {
+ if (!TextUtils.equals(learnMoreText, text)) {
+ learnMoreText = text
+ formatLearnMoreText()
+ }
+ }
+
+ private fun formatLearnMoreText() {
+ if (learnMoreListener == null || TextUtils.isEmpty(learnMoreText)) {
+ learnMoreTextView.visibility = GONE
+ isLearnMoreEnabled = false
+ return
+ }
+ val spannableLearnMoreText = SpannableString(learnMoreText)
+ if (learnMoreSpan != null) {
+ spannableLearnMoreText.removeSpan(learnMoreSpan)
+ }
+ learnMoreSpan = LearnMoreSpan(clickListener = learnMoreListener!!)
+ spannableLearnMoreText.setSpan(learnMoreSpan, 0, learnMoreText!!.length, 0)
+ learnMoreTextView.setText(spannableLearnMoreText)
+ learnMoreTextView.visibility = VISIBLE
+ isLearnMoreEnabled = true
+ }
+
+ private fun updateView() {
+ when {
+ isCollapsed -> {
+ collapseButton.apply {
+ text = collapseButtonResources.expandText
+ icon = collapseButtonResources.expandIcon
+ }
+ titleTextView.maxLines = minLines
+ }
+
+ else -> {
+ collapseButton.apply {
+ text = collapseButtonResources.collapseText
+ icon = collapseButtonResources.collapseIcon
+ }
+ titleTextView.maxLines = DEFAULT_MAX_LINES
+ }
+ }
+ collapseButton.visibility = if (isCollapsable) VISIBLE else GONE
+ learnMoreTextView.visibility = if (isLearnMoreEnabled && !isCollapsed) VISIBLE else GONE
+ }
+
+ private data class CollapseButtonResources(
+ val collapseIcon: Drawable,
+ val expandIcon: Drawable,
+ val collapseText: String,
+ val expandText: String
+ )
+
+ companion object {
+ private const val DEFAULT_MAX_LINES = 10
+ private const val DEFAULT_MIN_LINES = 2
+
+ private val Attrs = R.styleable.CollapsableTextView
+ private val GravityAttr = R.styleable.CollapsableTextView_android_gravity
+ }
+}
+
+internal class LearnMoreSpan(
+ val url: String = "",
+ val clickListener: View.OnClickListener) : URLSpan(url) {
+ override fun onClick(widget: View) {
+ if (clickListener != null) {
+ clickListener.onClick(widget)
+ }
+ }
+}
diff --git a/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/Expandable.kt b/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/Expandable.kt
new file mode 100644
index 000000000000..1f84118d877b
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/Expandable.kt
@@ -0,0 +1,21 @@
+/*
+ * 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.widget
+
+interface Expandable {
+ fun isExpanded(): Boolean
+} \ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/LinkableTextView.kt b/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/LinkableTextView.kt
new file mode 100644
index 000000000000..94a2d448bc6e
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/LinkableTextView.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.settingslib.widget
+
+import android.content.Context
+import android.text.Spanned
+import android.text.method.LinkMovementMethod
+import android.text.style.ClickableSpan
+import android.util.AttributeSet
+import android.widget.TextView
+
+class LinkableTextView @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0
+) : TextView(context, attrs, defStyleAttr) {
+
+ override fun setText(text: CharSequence, type: BufferType?) {
+ super.setText(text, type)
+ if (text is Spanned) {
+ val spans = text.getSpans(0, text.length, ClickableSpan::class.java)
+ if (spans.size > 0) {
+ movementMethod = LinkMovementMethod.getInstance()
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
index 7139f5b468ca..2a251a59e1d8 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
@@ -30,9 +30,6 @@ import com.android.settingslib.spa.gallery.editor.EditorMainPageProvider
import com.android.settingslib.spa.gallery.editor.SettingsDropdownBoxPageProvider
import com.android.settingslib.spa.gallery.editor.SettingsDropdownCheckBoxProvider
import com.android.settingslib.spa.gallery.home.HomePageProvider
-import com.android.settingslib.spa.gallery.itemList.ItemListPageProvider
-import com.android.settingslib.spa.gallery.itemList.ItemOperatePageProvider
-import com.android.settingslib.spa.gallery.itemList.OperateListPageProvider
import com.android.settingslib.spa.gallery.editor.SettingsOutlinedTextFieldPageProvider
import com.android.settingslib.spa.gallery.editor.SettingsTextFieldPasswordPageProvider
import com.android.settingslib.spa.gallery.page.ArgumentPageProvider
@@ -66,10 +63,6 @@ import com.android.settingslib.spa.gallery.ui.SpinnerPageProvider
*/
enum class SettingsPageProviderEnum(val displayName: String) {
HOME("home"),
- PREFERENCE("preference"),
- ARGUMENT("argument"),
- ITEM_LIST("itemList"),
- ITEM_OP_PAGE("itemOp"),
// Add your SPPs
}
@@ -101,9 +94,6 @@ class GallerySpaEnvironment(context: Context) : SpaEnvironment(context) {
ChartPageProvider,
DialogMainPageProvider,
NavDialogProvider,
- ItemListPageProvider,
- ItemOperatePageProvider,
- OperateListPageProvider,
EditorMainPageProvider,
SettingsOutlinedTextFieldPageProvider,
SettingsDropdownBoxPageProvider,
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/banner/BannerPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/banner/BannerPageProvider.kt
index 6edd9173d7e5..c16d8bfde23e 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/banner/BannerPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/banner/BannerPageProvider.kt
@@ -39,9 +39,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
-import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
import com.android.settingslib.spa.framework.common.SettingsPageProvider
-import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.framework.theme.SettingsTheme
@@ -161,14 +159,12 @@ object BannerPageProvider : SettingsPageProvider {
}
}
- fun buildInjectEntry(): SettingsEntryBuilder {
- return SettingsEntryBuilder.createInject(owner = createSettingsPage())
- .setUiLayoutFn {
- Preference(object : PreferenceModel {
- override val title = TITLE
- override val onClick = navigator(name)
- })
- }
+ @Composable
+ fun Entry() {
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ })
}
private const val TITLE = "Sample Banner"
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonPageProvider.kt
index b001caddd000..773d3d121a1d 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonPageProvider.kt
@@ -23,9 +23,7 @@ import androidx.compose.material.icons.outlined.Delete
import androidx.compose.material.icons.outlined.WarningAmber
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
-import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
import com.android.settingslib.spa.framework.common.SettingsPageProvider
-import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.widget.button.ActionButton
@@ -55,14 +53,12 @@ object ActionButtonPageProvider : SettingsPageProvider {
}
}
- fun buildInjectEntry(): SettingsEntryBuilder {
- return SettingsEntryBuilder.createInject(owner = createSettingsPage())
- .setUiLayoutFn {
- Preference(object : PreferenceModel {
- override val title = TITLE
- override val onClick = navigator(name)
- })
- }
+ @Composable
+ fun Entry() {
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ })
}
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/chart/ChartPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/chart/ChartPageProvider.kt
index 7a6ae2cee6ad..6ceb395272c4 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/chart/ChartPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/chart/ChartPageProvider.kt
@@ -39,6 +39,7 @@ import java.text.NumberFormat
private enum class WeekDay(val num: Int) {
Sun(0), Mon(1), Tue(2), Wed(3), Thu(4), Fri(5), Sat(6),
}
+
private const val TITLE = "Sample Chart"
object ChartPageProvider : SettingsPageProvider {
@@ -103,14 +104,12 @@ object ChartPageProvider : SettingsPageProvider {
return entryList
}
- fun buildInjectEntry(): SettingsEntryBuilder {
- return SettingsEntryBuilder.createInject(owner)
- .setUiLayoutFn {
- Preference(object : PreferenceModel {
- override val title = TITLE
- override val onClick = navigator(name)
- })
- }
+ @Composable
+ fun Entry() {
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ })
}
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/dialog/DialogMainPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/dialog/DialogMainPageProvider.kt
index 4e3fcee5383e..c9c81aac01c3 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/dialog/DialogMainPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/dialog/DialogMainPageProvider.kt
@@ -18,6 +18,7 @@ package com.android.settingslib.spa.gallery.dialog
import android.os.Bundle
import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
import com.android.settingslib.spa.framework.common.SettingsEntry
import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
import com.android.settingslib.spa.framework.common.SettingsPageProvider
@@ -55,13 +56,13 @@ object DialogMainPageProvider : SettingsPageProvider {
}.build(),
)
- fun buildInjectEntry() = SettingsEntryBuilder.createInject(owner)
- .setUiLayoutFn {
- Preference(object : PreferenceModel {
- override val title = TITLE
- override val onClick = navigator(name)
- })
- }
+ @Composable
+ fun Entry() {
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ })
+ }
override fun getTitle(arguments: Bundle?) = TITLE
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/EditorMainPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/EditorMainPageProvider.kt
index c511542f265a..f2b4091c7d85 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/EditorMainPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/EditorMainPageProvider.kt
@@ -17,8 +17,8 @@
package com.android.settingslib.spa.gallery.editor
import android.os.Bundle
+import androidx.compose.runtime.Composable
import com.android.settingslib.spa.framework.common.SettingsEntry
-import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
import com.android.settingslib.spa.framework.common.SettingsPageProvider
import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.navigator
@@ -44,14 +44,12 @@ object EditorMainPageProvider : SettingsPageProvider {
)
}
- fun buildInjectEntry(): SettingsEntryBuilder {
- return SettingsEntryBuilder.createInject(owner = owner)
- .setUiLayoutFn {
- Preference(object : PreferenceModel {
- override val title = TITLE
- override val onClick = navigator(name)
- })
- }
+ @Composable
+ fun Entry() {
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ })
}
override fun getTitle(arguments: Bundle?): String {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt
index b1558cce718a..4d77ea173a85 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt
@@ -20,20 +20,16 @@ import android.os.Bundle
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.tooling.preview.Preview
-import com.android.settingslib.spa.framework.common.SettingsEntry
import com.android.settingslib.spa.framework.common.SettingsPageProvider
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
-import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.gallery.R
import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
-import com.android.settingslib.spa.gallery.button.ActionButtonPageProvider
import com.android.settingslib.spa.gallery.banner.BannerPageProvider
+import com.android.settingslib.spa.gallery.button.ActionButtonPageProvider
import com.android.settingslib.spa.gallery.chart.ChartPageProvider
import com.android.settingslib.spa.gallery.dialog.DialogMainPageProvider
import com.android.settingslib.spa.gallery.editor.EditorMainPageProvider
-import com.android.settingslib.spa.gallery.itemList.OperateListPageProvider
-import com.android.settingslib.spa.gallery.page.ArgumentPageModel
import com.android.settingslib.spa.gallery.page.ArgumentPageProvider
import com.android.settingslib.spa.gallery.page.FooterPageProvider
import com.android.settingslib.spa.gallery.page.IllustrationPageProvider
@@ -48,35 +44,11 @@ import com.android.settingslib.spa.gallery.ui.CategoryPageProvider
import com.android.settingslib.spa.gallery.ui.CopyablePageProvider
import com.android.settingslib.spa.gallery.ui.SpinnerPageProvider
import com.android.settingslib.spa.widget.scaffold.HomeScaffold
+import com.android.settingslib.spa.widget.ui.Category
object HomePageProvider : SettingsPageProvider {
override val name = SettingsPageProviderEnum.HOME.name
override val displayName = SettingsPageProviderEnum.HOME.displayName
- private val owner = createSettingsPage()
-
- override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
- return listOf(
- PreferenceMainPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
- OperateListPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
- ArgumentPageProvider.buildInjectEntry("foo")!!.setLink(fromPage = owner).build(),
- SearchScaffoldPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
- SuwScaffoldPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
- SliderPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
- SpinnerPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
- PagerMainPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
- FooterPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
- IllustrationPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
- CategoryPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
- ActionButtonPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
- ProgressBarPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
- LoadingBarPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
- ChartPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
- DialogMainPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
- EditorMainPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
- BannerPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
- CopyablePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
- )
- }
override fun getTitle(arguments: Bundle?): String {
return SpaEnvironmentFactory.instance.appContext.getString(R.string.app_name)
@@ -85,14 +57,30 @@ object HomePageProvider : SettingsPageProvider {
@Composable
override fun Page(arguments: Bundle?) {
val title = remember { getTitle(arguments) }
- val entries = remember { buildEntry(arguments) }
HomeScaffold(title) {
- for (entry in entries) {
- if (entry.owner.isCreateBy(SettingsPageProviderEnum.ARGUMENT.name)) {
- entry.UiLayout(ArgumentPageModel.buildArgument(intParam = 0))
- } else {
- entry.UiLayout()
- }
+ Category {
+ PreferenceMainPageProvider.Entry()
+ }
+ Category {
+ SearchScaffoldPageProvider.Entry()
+ SuwScaffoldPageProvider.Entry()
+ ArgumentPageProvider.EntryItem(stringParam = "foo", intParam = 0)
+ }
+ Category {
+ SliderPageProvider.Entry()
+ SpinnerPageProvider.Entry()
+ PagerMainPageProvider.Entry()
+ FooterPageProvider.Entry()
+ IllustrationPageProvider.Entry()
+ CategoryPageProvider.Entry()
+ ActionButtonPageProvider.Entry()
+ ProgressBarPageProvider.Entry()
+ LoadingBarPageProvider.Entry()
+ ChartPageProvider.Entry()
+ DialogMainPageProvider.Entry()
+ EditorMainPageProvider.Entry()
+ BannerPageProvider.Entry()
+ CopyablePageProvider.Entry()
}
}
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/itemList/ItemListPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/itemList/ItemListPage.kt
deleted file mode 100644
index 5f251b1b14dd..000000000000
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/itemList/ItemListPage.kt
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.spa.gallery.itemList
-
-import android.os.Bundle
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.remember
-import androidx.core.os.bundleOf
-import androidx.navigation.NavType
-import androidx.navigation.navArgument
-import com.android.settingslib.spa.framework.common.EntrySearchData
-import com.android.settingslib.spa.framework.common.SettingsEntry
-import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
-import com.android.settingslib.spa.framework.common.SettingsPageProvider
-import com.android.settingslib.spa.framework.common.createSettingsPage
-import com.android.settingslib.spa.framework.compose.navigator
-import com.android.settingslib.spa.framework.util.getStringArg
-import com.android.settingslib.spa.framework.util.navLink
-import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
-import com.android.settingslib.spa.widget.preference.Preference
-import com.android.settingslib.spa.widget.preference.PreferenceModel
-import com.android.settingslib.spa.widget.scaffold.RegularScaffold
-
-private const val OPERATOR_PARAM_NAME = "opParam"
-
-object ItemListPageProvider : SettingsPageProvider {
- override val name = SettingsPageProviderEnum.ITEM_LIST.name
- override val displayName = SettingsPageProviderEnum.ITEM_LIST.displayName
- override val parameter = listOf(
- navArgument(OPERATOR_PARAM_NAME) { type = NavType.StringType },
- )
-
- override fun getTitle(arguments: Bundle?): String {
- val operation = parameter.getStringArg(OPERATOR_PARAM_NAME, arguments) ?: "NULL"
- return "Operation: $operation"
- }
-
- override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
- if (!ItemOperatePageProvider.isValidArgs(arguments)) return emptyList()
- val operation = parameter.getStringArg(OPERATOR_PARAM_NAME, arguments)!!
- val owner = createSettingsPage(arguments)
- return listOf(
- ItemOperatePageProvider.buildInjectEntry(operation)!!.setLink(fromPage = owner).build(),
- )
- }
-
- fun buildInjectEntry(opParam: String): SettingsEntryBuilder? {
- val arguments = bundleOf(OPERATOR_PARAM_NAME to opParam)
- if (!ItemOperatePageProvider.isValidArgs(arguments)) return null
-
- return SettingsEntryBuilder.createInject(
- owner = createSettingsPage(arguments),
- label = "ItemList_$opParam",
- ).setUiLayoutFn {
- Preference(
- object : PreferenceModel {
- override val title = opParam
- override val onClick = navigator(
- SettingsPageProviderEnum.ITEM_LIST.name + parameter.navLink(it)
- )
- }
- )
- }.setSearchDataFn {
- EntrySearchData(title = "Operation: $opParam")
- }
- }
-
- @Composable
- override fun Page(arguments: Bundle?) {
- val title = remember { getTitle(arguments) }
- val entries = remember { buildEntry(arguments) }
- val itemList = remember {
- // Add logic to get item List during runtime.
- listOf("itemFoo", "itemBar", "itemToy")
- }
- RegularScaffold(title) {
- for (item in itemList) {
- val rtArgs = ItemOperatePageProvider.genRuntimeArguments(item)
- for (entry in entries) {
- entry.UiLayout(rtArgs)
- }
- }
- }
- }
-}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/itemList/ItemOperatePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/itemList/ItemOperatePage.kt
deleted file mode 100644
index 6caec07371f5..000000000000
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/itemList/ItemOperatePage.kt
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.spa.gallery.itemList
-
-import android.os.Bundle
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.saveable.rememberSaveable
-import androidx.compose.runtime.setValue
-import androidx.core.os.bundleOf
-import androidx.navigation.NavType
-import androidx.navigation.navArgument
-import com.android.settingslib.spa.framework.common.SettingsEntry
-import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
-import com.android.settingslib.spa.framework.common.SettingsPageProvider
-import com.android.settingslib.spa.framework.common.createSettingsPage
-import com.android.settingslib.spa.framework.compose.navigator
-import com.android.settingslib.spa.framework.util.getStringArg
-import com.android.settingslib.spa.framework.util.navLink
-import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
-import com.android.settingslib.spa.widget.preference.Preference
-import com.android.settingslib.spa.widget.preference.PreferenceModel
-import com.android.settingslib.spa.widget.preference.SwitchPreference
-import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
-
-private const val OPERATOR_PARAM_NAME = "opParam"
-private const val ITEM_NAME_PARAM_NAME = "rt_nameParam"
-private val ALLOWED_OPERATOR_LIST = listOf("opDnD", "opPiP", "opInstall", "opConnect")
-
-object ItemOperatePageProvider : SettingsPageProvider {
- override val name = SettingsPageProviderEnum.ITEM_OP_PAGE.name
- override val displayName = SettingsPageProviderEnum.ITEM_OP_PAGE.displayName
- override val parameter = listOf(
- navArgument(OPERATOR_PARAM_NAME) { type = NavType.StringType },
- navArgument(ITEM_NAME_PARAM_NAME) { type = NavType.StringType },
- )
-
- override fun getTitle(arguments: Bundle?): String {
- // Operation name is not a runtime parameter, which should always available
- val operation = parameter.getStringArg(OPERATOR_PARAM_NAME, arguments) ?: "opInValid"
- // Item name is a runtime parameter, which could be missing
- val itemName = parameter.getStringArg(ITEM_NAME_PARAM_NAME, arguments) ?: "[unset]"
- return "$operation on $itemName"
- }
-
- override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
- if (!isValidArgs(arguments)) return emptyList()
-
- val owner = createSettingsPage(arguments)
- val entryList = mutableListOf<SettingsEntry>()
- entryList.add(
- SettingsEntryBuilder.create("ItemName", owner)
- .setUiLayoutFn {
- // Item name is a runtime parameter, which needs to be read inside UiLayoutFn
- val itemName = parameter.getStringArg(ITEM_NAME_PARAM_NAME, it) ?: "NULL"
- Preference(
- object : PreferenceModel {
- override val title = "Item $itemName"
- }
- )
- }.build()
- )
-
- // Operation name is not a runtime parameter, which can be read outside.
- val opName = parameter.getStringArg(OPERATOR_PARAM_NAME, arguments)!!
- entryList.add(
- SettingsEntryBuilder.create("ItemOp", owner)
- .setUiLayoutFn {
- var checked by rememberSaveable { mutableStateOf(false) }
- SwitchPreference(remember {
- object : SwitchPreferenceModel {
- override val title = "Item operation: $opName"
- override val checked = { checked }
- override val onCheckedChange =
- { newChecked: Boolean -> checked = newChecked }
- }
- })
- }.build(),
- )
- return entryList
- }
-
- fun buildInjectEntry(opParam: String): SettingsEntryBuilder? {
- val arguments = bundleOf(OPERATOR_PARAM_NAME to opParam)
- if (!isValidArgs(arguments)) return null
-
- return SettingsEntryBuilder.createInject(
- owner = createSettingsPage(arguments),
- label = "ItemOp_$opParam",
- ).setUiLayoutFn {
- // Item name is a runtime parameter, which needs to be read inside UiLayoutFn
- val itemName = parameter.getStringArg(ITEM_NAME_PARAM_NAME, it) ?: "NULL"
- Preference(
- object : PreferenceModel {
- override val title = "item: $itemName"
- override val onClick = navigator(
- SettingsPageProviderEnum.ITEM_OP_PAGE.name + parameter.navLink(it)
- )
- }
- )
- }
- }
-
- fun isValidArgs(arguments: Bundle?): Boolean {
- val opParam = parameter.getStringArg(OPERATOR_PARAM_NAME, arguments)
- return (opParam != null && ALLOWED_OPERATOR_LIST.contains(opParam))
- }
-
- fun genRuntimeArguments(itemName: String): Bundle {
- return bundleOf(ITEM_NAME_PARAM_NAME to itemName)
- }
-}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/itemList/OperateListPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/itemList/OperateListPage.kt
deleted file mode 100644
index e0baf86119a3..000000000000
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/itemList/OperateListPage.kt
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.spa.gallery.itemList
-
-import android.os.Bundle
-import com.android.settingslib.spa.framework.common.SettingsEntry
-import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
-import com.android.settingslib.spa.framework.common.SettingsPageProvider
-import com.android.settingslib.spa.framework.common.createSettingsPage
-import com.android.settingslib.spa.framework.compose.navigator
-import com.android.settingslib.spa.widget.preference.Preference
-import com.android.settingslib.spa.widget.preference.PreferenceModel
-
-private const val TITLE = "Operate List Main"
-
-object OperateListPageProvider : SettingsPageProvider {
- override val name = "OpList"
- private val owner = createSettingsPage()
-
- override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
- return listOf(
- ItemListPageProvider.buildInjectEntry("opPiP")!!.setLink(fromPage = owner).build(),
- ItemListPageProvider.buildInjectEntry("opInstall")!!.setLink(fromPage = owner).build(),
- ItemListPageProvider.buildInjectEntry("opDnD")!!.setLink(fromPage = owner).build(),
- ItemListPageProvider.buildInjectEntry("opConnect")!!.setLink(fromPage = owner).build(),
- )
- }
-
- override fun getTitle(arguments: Bundle?): String {
- return TITLE
- }
-
- fun buildInjectEntry(): SettingsEntryBuilder {
- return SettingsEntryBuilder.createInject(owner = owner)
- .setUiLayoutFn {
- Preference(object : PreferenceModel {
- override val title = TITLE
- override val onClick = navigator(name)
- })
- }
- }
-}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt
index f01ff3849701..9ad1c22a4912 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt
@@ -18,112 +18,69 @@ package com.android.settingslib.spa.gallery.page
import android.os.Bundle
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.remember
import androidx.compose.ui.tooling.preview.Preview
-import com.android.settingslib.spa.framework.common.SettingsEntry
-import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
-import com.android.settingslib.spa.framework.common.SettingsPage
+import androidx.navigation.NavType
+import androidx.navigation.navArgument
import com.android.settingslib.spa.framework.common.SettingsPageProvider
-import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
-import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.theme.SettingsTheme
-import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spa.widget.scaffold.RegularScaffold
-object ArgumentPageProvider : SettingsPageProvider {
- // Defines all entry name in this page.
- // Note that entry name would be used in log. DO NOT change it once it is set.
- // One can still change the display name for better readability if necessary.
- private enum class EntryEnum(val displayName: String) {
- STRING_PARAM("string_param"),
- INT_PARAM("int_param"),
- }
-
- private fun createEntry(owner: SettingsPage, entry: EntryEnum): SettingsEntryBuilder {
- return SettingsEntryBuilder.create(owner, entry.name, entry.displayName)
- }
-
- override val name = SettingsPageProviderEnum.ARGUMENT.name
- override val displayName = SettingsPageProviderEnum.ARGUMENT.displayName
- override val parameter = ArgumentPageModel.parameter
+private const val TITLE = "Sample page with arguments"
+private const val STRING_PARAM_NAME = "stringParam"
+private const val INT_PARAM_NAME = "intParam"
- override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
- if (!ArgumentPageModel.isValidArgument(arguments)) return emptyList()
+object ArgumentPageProvider : SettingsPageProvider {
+ override val name = "Argument"
- val owner = createSettingsPage(arguments)
- val entryList = mutableListOf<SettingsEntry>()
- entryList.add(
- createEntry(owner, EntryEnum.STRING_PARAM)
- // Set attributes
- .setIsSearchDataDynamic(true)
- .setSearchDataFn { ArgumentPageModel.genStringParamSearchData() }
- .setUiLayoutFn {
- // Set ui rendering
- Preference(ArgumentPageModel.create(it).genStringParamPreferenceModel())
- }.build()
- )
+ override val parameter = listOf(
+ navArgument(STRING_PARAM_NAME) { type = NavType.StringType },
+ navArgument(INT_PARAM_NAME) { type = NavType.IntType },
+ )
- entryList.add(
- createEntry(owner, EntryEnum.INT_PARAM)
- // Set attributes
- .setIsSearchDataDynamic(true)
- .setSearchDataFn { ArgumentPageModel.genIntParamSearchData() }
- .setUiLayoutFn {
- // Set ui rendering
- Preference(ArgumentPageModel.create(it).genIntParamPreferenceModel())
- }.build()
+ @Composable
+ override fun Page(arguments: Bundle?) {
+ ArgumentPage(
+ stringParam = arguments!!.getString(STRING_PARAM_NAME, "default"),
+ intParam = arguments.getInt(INT_PARAM_NAME),
)
+ }
- entryList.add(buildInjectEntry("foo")!!.setLink(fromPage = owner).build())
- entryList.add(buildInjectEntry("bar")!!.setLink(fromPage = owner).build())
-
- return entryList
+ @Composable
+ fun EntryItem(stringParam: String, intParam: Int) {
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val summary = { "$STRING_PARAM_NAME=$stringParam, $INT_PARAM_NAME=$intParam" }
+ override val onClick = navigator("$name/$stringParam/$intParam")
+ })
}
+}
- fun buildInjectEntry(stringParam: String): SettingsEntryBuilder? {
- val arguments = ArgumentPageModel.buildArgument(stringParam)
- if (!ArgumentPageModel.isValidArgument(arguments)) return null
+@Composable
+fun ArgumentPage(stringParam: String, intParam: Int) {
+ RegularScaffold(title = TITLE) {
+ Preference(object : PreferenceModel {
+ override val title = "String param value"
+ override val summary = { stringParam }
+ })
- return SettingsEntryBuilder.createInject(
- owner = createSettingsPage(arguments),
- label = "${name}_$stringParam",
- )
- .setSearchDataFn { ArgumentPageModel.genInjectSearchData() }
- .setUiLayoutFn {
- // Set ui rendering
- Preference(ArgumentPageModel.create(it).genInjectPreferenceModel())
- }
- }
+ Preference(object : PreferenceModel {
+ override val title = "Int param value"
+ override val summary = { intParam.toString() }
+ })
- override fun getTitle(arguments: Bundle?): String {
- return ArgumentPageModel.genPageTitle()
- }
+ ArgumentPageProvider.EntryItem(stringParam = "foo", intParam = intParam + 1)
- @Composable
- override fun Page(arguments: Bundle?) {
- val title = remember { getTitle(arguments) }
- val entries = remember { buildEntry(arguments) }
- val rtArgNext = remember { ArgumentPageModel.buildNextArgument(arguments) }
- RegularScaffold(title) {
- for (entry in entries) {
- if (entry.toPage != null) {
- entry.UiLayout(rtArgNext)
- } else {
- entry.UiLayout()
- }
- }
- }
+ ArgumentPageProvider.EntryItem(stringParam = "bar", intParam = intParam + 1)
}
}
@Preview(showBackground = true)
@Composable
private fun ArgumentPagePreview() {
- SpaEnvironmentFactory.resetForPreview()
SettingsTheme {
- ArgumentPageProvider.Page(
- ArgumentPageModel.buildArgument(stringParam = "foo", intParam = 0)
- )
+ ArgumentPage(stringParam = "foo", intParam = 0)
}
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPageModel.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPageModel.kt
deleted file mode 100644
index d763f77d2644..000000000000
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPageModel.kt
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.spa.gallery.page
-
-import android.os.Bundle
-import androidx.compose.runtime.Composable
-import androidx.lifecycle.viewmodel.compose.viewModel
-import androidx.navigation.NavType
-import androidx.navigation.navArgument
-import com.android.settingslib.spa.framework.common.EntrySearchData
-import com.android.settingslib.spa.framework.common.PageModel
-import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
-import com.android.settingslib.spa.framework.compose.navigator
-import com.android.settingslib.spa.framework.util.getIntArg
-import com.android.settingslib.spa.framework.util.getStringArg
-import com.android.settingslib.spa.framework.util.navLink
-import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
-import com.android.settingslib.spa.widget.preference.PreferenceModel
-
-private const val TAG = "ArgumentPageModel"
-
-// Defines all the resources for this page.
-// In real Settings App, resources data is defined in xml, rather than SPP.
-private const val PAGE_TITLE = "Sample page with arguments"
-private const val STRING_PARAM_TITLE = "String param value"
-private const val INT_PARAM_TITLE = "Int param value"
-private const val STRING_PARAM_NAME = "stringParam"
-private const val INT_PARAM_NAME = "rt_intParam"
-private val ARGUMENT_PAGE_KEYWORDS = listOf("argument keyword1", "argument keyword2")
-
-class ArgumentPageModel : PageModel() {
-
- companion object {
- val parameter = listOf(
- navArgument(STRING_PARAM_NAME) { type = NavType.StringType },
- navArgument(INT_PARAM_NAME) { type = NavType.IntType },
- )
-
- fun buildArgument(stringParam: String? = null, intParam: Int? = null): Bundle {
- val args = Bundle()
- if (stringParam != null) args.putString(STRING_PARAM_NAME, stringParam)
- if (intParam != null) args.putInt(INT_PARAM_NAME, intParam)
- return args
- }
-
- fun buildNextArgument(arguments: Bundle? = null): Bundle {
- val intParam = parameter.getIntArg(INT_PARAM_NAME, arguments)
- val nextIntParam = if (intParam != null) intParam + 1 else null
- return buildArgument(intParam = nextIntParam)
- }
-
- fun isValidArgument(arguments: Bundle?): Boolean {
- val stringParam = parameter.getStringArg(STRING_PARAM_NAME, arguments)
- return (stringParam != null && listOf("foo", "bar").contains(stringParam))
- }
-
- fun genStringParamSearchData(): EntrySearchData {
- return EntrySearchData(title = STRING_PARAM_TITLE)
- }
-
- fun genIntParamSearchData(): EntrySearchData {
- return EntrySearchData(title = INT_PARAM_TITLE)
- }
-
- fun genInjectSearchData(): EntrySearchData {
- return EntrySearchData(title = PAGE_TITLE, keyword = ARGUMENT_PAGE_KEYWORDS)
- }
-
- fun genPageTitle(): String {
- return PAGE_TITLE
- }
-
- @Composable
- fun create(arguments: Bundle?): ArgumentPageModel {
- val pageModel: ArgumentPageModel = viewModel(key = arguments.toString())
- pageModel.initOnce(arguments)
- return pageModel
- }
- }
-
- private var arguments: Bundle? = null
- private var stringParam: String? = null
- private var intParam: Int? = null
-
- override fun initialize(arguments: Bundle?) {
- SpaEnvironmentFactory.instance.logger.message(
- TAG, "Initialize with args " + arguments.toString()
- )
- this.arguments = arguments
- stringParam = parameter.getStringArg(STRING_PARAM_NAME, arguments)
- intParam = parameter.getIntArg(INT_PARAM_NAME, arguments)
- }
-
- @Composable
- fun genStringParamPreferenceModel(): PreferenceModel {
- return object : PreferenceModel {
- override val title = STRING_PARAM_TITLE
- override val summary = { stringParam!! }
- }
- }
-
- @Composable
- fun genIntParamPreferenceModel(): PreferenceModel {
- return object : PreferenceModel {
- override val title = INT_PARAM_TITLE
- override val summary = { intParam!!.toString() }
- }
- }
-
- @Composable
- fun genInjectPreferenceModel(): PreferenceModel {
- val summaryArray = listOf(
- "$STRING_PARAM_NAME=" + stringParam!!,
- "$INT_PARAM_NAME=" + intParam!!
- )
- return object : PreferenceModel {
- override val title = PAGE_TITLE
- override val summary = { summaryArray.joinToString(", ") }
- override val onClick = navigator(
- SettingsPageProviderEnum.ARGUMENT.name + parameter.navLink(arguments)
- )
- }
- }
-}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPageProvider.kt
index 345b47aff791..d31dab31669c 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPageProvider.kt
@@ -43,7 +43,7 @@ object FooterPageProvider : SettingsPageProvider {
override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
val entryList = mutableListOf<SettingsEntry>()
entryList.add(
- SettingsEntryBuilder.create( "Some Preference", owner)
+ SettingsEntryBuilder.create("Some Preference", owner)
.setSearchDataFn { EntrySearchData(title = "Some Preference") }
.setUiLayoutFn {
Preference(remember {
@@ -58,14 +58,12 @@ object FooterPageProvider : SettingsPageProvider {
return entryList
}
- fun buildInjectEntry(): SettingsEntryBuilder {
- return SettingsEntryBuilder.createInject(owner)
- .setUiLayoutFn {
- Preference(object : PreferenceModel {
- override val title = TITLE
- override val onClick = navigator(name)
- })
- }
+ @Composable
+ fun Entry() {
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ })
}
override fun getTitle(arguments: Bundle?): String {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/IllustrationPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/IllustrationPageProvider.kt
index ee22b96c937f..021e84f81808 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/IllustrationPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/IllustrationPageProvider.kt
@@ -41,7 +41,7 @@ object IllustrationPageProvider : SettingsPageProvider {
override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
val entryList = mutableListOf<SettingsEntry>()
entryList.add(
- SettingsEntryBuilder.create( "Lottie Illustration", owner)
+ SettingsEntryBuilder.create("Lottie Illustration", owner)
.setUiLayoutFn {
Preference(object : PreferenceModel {
override val title = "Lottie Illustration"
@@ -54,7 +54,7 @@ object IllustrationPageProvider : SettingsPageProvider {
}.build()
)
entryList.add(
- SettingsEntryBuilder.create( "Image Illustration", owner)
+ SettingsEntryBuilder.create("Image Illustration", owner)
.setUiLayoutFn {
Preference(object : PreferenceModel {
override val title = "Image Illustration"
@@ -70,14 +70,12 @@ object IllustrationPageProvider : SettingsPageProvider {
return entryList
}
- fun buildInjectEntry(): SettingsEntryBuilder {
- return SettingsEntryBuilder.createInject(owner)
- .setUiLayoutFn {
- Preference(object : PreferenceModel {
- override val title = TITLE
- override val onClick = navigator(name)
- })
- }
+ @Composable
+ fun Entry() {
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ })
}
override fun getTitle(arguments: Bundle?): String {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/LoadingBarPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/LoadingBarPageProvider.kt
index f1cbc3729a78..4d474816082f 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/LoadingBarPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/LoadingBarPageProvider.kt
@@ -30,9 +30,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
-import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
import com.android.settingslib.spa.framework.common.SettingsPageProvider
-import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.framework.theme.SettingsTheme
@@ -47,14 +45,12 @@ private const val TITLE = "Sample LoadingBar"
object LoadingBarPageProvider : SettingsPageProvider {
override val name = "LoadingBar"
- fun buildInjectEntry(): SettingsEntryBuilder {
- return SettingsEntryBuilder.createInject(owner = createSettingsPage())
- .setUiLayoutFn {
- Preference(object : PreferenceModel {
- override val title = TITLE
- override val onClick = navigator(name)
- })
- }
+ @Composable
+ fun Entry() {
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ })
}
override fun getTitle(arguments: Bundle?): String {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ProgressBarPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ProgressBarPageProvider.kt
index 9026a240a04a..47c49fea3e5a 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ProgressBarPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ProgressBarPageProvider.kt
@@ -27,9 +27,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.tooling.preview.Preview
-import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
import com.android.settingslib.spa.framework.common.SettingsPageProvider
-import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.widget.preference.Preference
@@ -46,14 +44,12 @@ private const val TITLE = "Sample ProgressBar"
object ProgressBarPageProvider : SettingsPageProvider {
override val name = "ProgressBar"
- fun buildInjectEntry(): SettingsEntryBuilder {
- return SettingsEntryBuilder.createInject(owner = createSettingsPage())
- .setUiLayoutFn {
- Preference(object : PreferenceModel {
- override val title = TITLE
- override val onClick = navigator(name)
- })
- }
+ @Composable
+ fun Entry() {
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ })
}
override fun getTitle(arguments: Bundle?): String {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPageProvider.kt
index 89b10ee2cb84..572746b14535 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPageProvider.kt
@@ -117,15 +117,14 @@ object SliderPageProvider : SettingsPageProvider {
return entryList
}
- fun buildInjectEntry(): SettingsEntryBuilder {
- return SettingsEntryBuilder.createInject(owner).setUiLayoutFn {
- Preference(
- object : PreferenceModel {
- override val title = TITLE
- override val onClick = navigator(name)
- }
- )
- }
+ @Composable
+ fun Entry() {
+ Preference(
+ object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ }
+ )
}
override fun getTitle(arguments: Bundle?): String {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/IntroPreferencePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/IntroPreferencePageProvider.kt
index 603fceed9900..b83a02637371 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/IntroPreferencePageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/IntroPreferencePageProvider.kt
@@ -50,15 +50,12 @@ object IntroPreferencePageProvider : SettingsPageProvider {
return entryList
}
- fun buildInjectEntry(): SettingsEntryBuilder {
- return SettingsEntryBuilder.createInject(owner).setUiLayoutFn {
- Preference(
- object : PreferenceModel {
- override val title = TITLE
- override val onClick = navigator(name)
- }
- )
- }
+ @Composable
+ fun Entry() {
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ })
}
override fun getTitle(arguments: Bundle?): String {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/ListPreferencePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/ListPreferencePageProvider.kt
index d7de9b4f2045..3bb526ef7996 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/ListPreferencePageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/ListPreferencePageProvider.kt
@@ -23,9 +23,7 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.tooling.preview.Preview
import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
import com.android.settingslib.spa.framework.common.SettingsPageProvider
-import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.widget.preference.ListPreference
@@ -33,6 +31,8 @@ import com.android.settingslib.spa.widget.preference.ListPreferenceModel
import com.android.settingslib.spa.widget.preference.ListPreferenceOption
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.scaffold.RegularScaffold
+import com.android.settingslib.spa.widget.ui.Category
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.flow
@@ -41,30 +41,24 @@ private const val TITLE = "Sample ListPreference"
object ListPreferencePageProvider : SettingsPageProvider {
override val name = "ListPreference"
- private val owner = createSettingsPage()
- override fun buildEntry(arguments: Bundle?) = listOf(
- SettingsEntryBuilder.create("ListPreference", owner)
- .setUiLayoutFn {
+ @Composable
+ override fun Page(arguments: Bundle?) {
+ RegularScaffold(TITLE) {
+ Category {
SampleListPreference()
- }.build(),
- SettingsEntryBuilder.create("ListPreference not changeable", owner)
- .setUiLayoutFn {
SampleNotChangeableListPreference()
- }.build(),
- )
-
- fun buildInjectEntry(): SettingsEntryBuilder {
- return SettingsEntryBuilder.createInject(owner)
- .setUiLayoutFn {
- Preference(object : PreferenceModel {
- override val title = TITLE
- override val onClick = navigator(name)
- })
}
+ }
}
- override fun getTitle(arguments: Bundle?) = TITLE
+ @Composable
+ fun Entry() {
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ })
+ }
}
@Composable
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/MainSwitchPreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/MainSwitchPreferencePageProvider.kt
index 0d85c0e3262f..f548160ef336 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/MainSwitchPreferencePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/MainSwitchPreferencePageProvider.kt
@@ -59,14 +59,12 @@ object MainSwitchPreferencePageProvider : SettingsPageProvider {
return entryList
}
- fun buildInjectEntry(): SettingsEntryBuilder {
- return SettingsEntryBuilder.createInject(owner)
- .setUiLayoutFn {
- Preference(object : PreferenceModel {
- override val title = TITLE
- override val onClick = navigator(name)
- })
- }
+ @Composable
+ fun Entry() {
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ })
}
override fun getTitle(arguments: Bundle?): String {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMainPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMainPageProvider.kt
index 1626b025e2f7..831b43942e98 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMainPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMainPageProvider.kt
@@ -17,45 +17,44 @@
package com.android.settingslib.spa.gallery.preference
import android.os.Bundle
-import com.android.settingslib.spa.framework.common.SettingsEntry
-import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import androidx.compose.runtime.Composable
import com.android.settingslib.spa.framework.common.SettingsPageProvider
-import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.scaffold.RegularScaffold
+import com.android.settingslib.spa.widget.ui.Category
private const val TITLE = "Category: Preference"
object PreferenceMainPageProvider : SettingsPageProvider {
override val name = "PreferenceMain"
- private val owner = createSettingsPage()
- override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
- return listOf(
- PreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
- SwitchPreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
- MainSwitchPreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
- ListPreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
- TwoTargetSwitchPreferencePageProvider.buildInjectEntry()
- .setLink(fromPage = owner).build(),
- ZeroStatePreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
- IntroPreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
- TopIntroPreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
- )
- }
-
- fun buildInjectEntry(): SettingsEntryBuilder {
- return SettingsEntryBuilder.createInject(owner = owner)
- .setUiLayoutFn {
- Preference(object : PreferenceModel {
- override val title = TITLE
- override val onClick = navigator(name)
- })
+ @Composable
+ override fun Page(arguments: Bundle?) {
+ RegularScaffold(TITLE) {
+ Category {
+ PreferencePageProvider.Entry()
+ ListPreferencePageProvider.Entry()
+ }
+ Category {
+ SwitchPreferencePageProvider.Entry()
+ MainSwitchPreferencePageProvider.Entry()
+ TwoTargetSwitchPreferencePageProvider.Entry()
+ }
+ Category {
+ ZeroStatePreferencePageProvider.Entry()
+ IntroPreferencePageProvider.Entry()
+ TopIntroPreferencePageProvider.Entry()
}
+ }
}
- override fun getTitle(arguments: Bundle?): String {
- return TITLE
+ @Composable
+ fun Entry() {
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ })
}
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageModel.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageModel.kt
deleted file mode 100644
index fc6f10f79ceb..000000000000
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageModel.kt
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.spa.gallery.preference
-
-import android.os.Bundle
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.State
-import androidx.compose.runtime.derivedStateOf
-import androidx.compose.runtime.mutableStateOf
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.viewModelScope
-import androidx.lifecycle.viewmodel.compose.viewModel
-import com.android.settingslib.spa.framework.common.PageModel
-import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.launch
-
-private const val TAG = "PreferencePageModel"
-
-class PreferencePageModel : PageModel() {
- companion object {
- // Defines all the resources for this page.
- // In real Settings App, resources data is defined in xml, rather than SPP.
- const val PAGE_TITLE = "Sample Preference"
- const val SIMPLE_PREFERENCE_TITLE = "Preference"
- const val SIMPLE_PREFERENCE_SUMMARY = "Simple summary"
- const val DISABLE_PREFERENCE_TITLE = "Disabled"
- const val DISABLE_PREFERENCE_SUMMARY = "Disabled summary"
- const val ASYNC_PREFERENCE_TITLE = "Async Preference"
- const val ASYNC_PREFERENCE_SUMMARY = "Async summary"
- const val MANUAL_UPDATE_PREFERENCE_TITLE = "Manual Updater"
- const val AUTO_UPDATE_PREFERENCE_TITLE = "Auto Updater"
- val SIMPLE_PREFERENCE_KEYWORDS = listOf("simple keyword1", "simple keyword2")
-
- @Composable
- fun create(): PreferencePageModel {
- val pageModel: PreferencePageModel = viewModel()
- pageModel.initOnce()
- return pageModel
- }
- }
-
- private val spaLogger = SpaEnvironmentFactory.instance.logger
-
- val asyncSummary = mutableStateOf("(loading)")
- val asyncEnable = mutableStateOf(false)
-
- private val manualUpdater = mutableStateOf(0)
-
- private val autoUpdater = object : MutableLiveData<String>(" ") {
- private var tick = 0
- private var updateJob: Job? = null
- override fun onActive() {
- spaLogger.message(TAG, "autoUpdater.active")
- updateJob = viewModelScope.launch(Dispatchers.IO) {
- while (true) {
- delay(1000L)
- tick++
- spaLogger.message(TAG, "autoUpdater.value $tick")
- postValue(tick.toString())
- }
- }
- }
-
- override fun onInactive() {
- spaLogger.message(TAG, "autoUpdater.inactive")
- updateJob?.cancel()
- }
- }
-
- override fun initialize(arguments: Bundle?) {
- spaLogger.message(TAG, "initialize with args " + arguments.toString())
- viewModelScope.launch(Dispatchers.IO) {
- // Loading your data here.
- delay(2000L)
- asyncSummary.value = ASYNC_PREFERENCE_SUMMARY
- asyncEnable.value = true
- }
- }
-
- fun getManualUpdaterSummary(): State<String> {
- spaLogger.message(TAG, "getManualUpdaterSummary")
- return derivedStateOf { manualUpdater.value.toString() }
- }
-
- fun manualUpdaterOnClick() {
- spaLogger.message(TAG, "manualUpdaterOnClick")
- manualUpdater.value = manualUpdater.value + 1
- }
-
- fun getAutoUpdaterSummary(): LiveData<String> {
- spaLogger.message(TAG, "getAutoUpdaterSummary")
- return autoUpdater
- }
-}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageProvider.kt
index 6d1d34628efa..f7649b91f558 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageProvider.kt
@@ -18,187 +18,100 @@ package com.android.settingslib.spa.gallery.preference
import android.os.Bundle
import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.outlined.Autorenew
import androidx.compose.material.icons.outlined.DisabledByDefault
-import androidx.compose.material.icons.outlined.TouchApp
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.livedata.observeAsState
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
-import com.android.settingslib.spa.framework.common.EntrySearchData
-import com.android.settingslib.spa.framework.common.EntryStatusData
-import com.android.settingslib.spa.framework.common.SettingsEntry
-import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
import com.android.settingslib.spa.framework.common.SettingsPageProvider
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
-import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.gallery.R
-import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
-import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.ASYNC_PREFERENCE_TITLE
-import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.AUTO_UPDATE_PREFERENCE_TITLE
-import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.DISABLE_PREFERENCE_SUMMARY
-import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.DISABLE_PREFERENCE_TITLE
-import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.MANUAL_UPDATE_PREFERENCE_TITLE
-import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.PAGE_TITLE
-import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.SIMPLE_PREFERENCE_KEYWORDS
-import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.SIMPLE_PREFERENCE_SUMMARY
-import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.SIMPLE_PREFERENCE_TITLE
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
-import com.android.settingslib.spa.widget.preference.SimplePreferenceMacro
+import com.android.settingslib.spa.widget.scaffold.RegularScaffold
+import com.android.settingslib.spa.widget.ui.Category
import com.android.settingslib.spa.widget.ui.SettingsIcon
-
-private const val TAG = "PreferencePage"
+import kotlinx.coroutines.delay
object PreferencePageProvider : SettingsPageProvider {
- // Defines all entry name in this page.
- // Note that entry name would be used in log. DO NOT change it once it is set.
- // One can still change the display name for better readability if necessary.
- enum class EntryEnum(val displayName: String) {
- SIMPLE_PREFERENCE("preference"),
- SUMMARY_PREFERENCE("preference_with_summary"),
- SINGLE_LINE_SUMMARY_PREFERENCE("preference_with_single_line_summary"),
- DISABLED_PREFERENCE("preference_disable"),
- ASYNC_SUMMARY_PREFERENCE("preference_with_async_summary"),
- MANUAL_UPDATE_PREFERENCE("preference_actionable"),
- AUTO_UPDATE_PREFERENCE("preference_auto_update"),
- }
-
- override val name = SettingsPageProviderEnum.PREFERENCE.name
- override val displayName = SettingsPageProviderEnum.PREFERENCE.displayName
- private val spaLogger = SpaEnvironmentFactory.instance.logger
- private val owner = createSettingsPage()
-
- private fun createEntry(entry: EntryEnum): SettingsEntryBuilder {
- return SettingsEntryBuilder.create(owner, entry.name, entry.displayName)
- }
- override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
- val entryList = mutableListOf<SettingsEntry>()
- entryList.add(
- createEntry(EntryEnum.SIMPLE_PREFERENCE)
- .setMacro {
- spaLogger.message(TAG, "create macro for ${EntryEnum.SIMPLE_PREFERENCE}")
- SimplePreferenceMacro(title = SIMPLE_PREFERENCE_TITLE)
- }
- .setStatusDataFn { EntryStatusData(isDisabled = false) }
- .build()
- )
- entryList.add(
- createEntry(EntryEnum.SUMMARY_PREFERENCE)
- .setMacro {
- spaLogger.message(TAG, "create macro for ${EntryEnum.SUMMARY_PREFERENCE}")
- SimplePreferenceMacro(
- title = SIMPLE_PREFERENCE_TITLE,
- summary = SIMPLE_PREFERENCE_SUMMARY,
- searchKeywords = SIMPLE_PREFERENCE_KEYWORDS,
- )
- }
- .setStatusDataFn { EntryStatusData(isDisabled = true) }
- .build()
- )
- entryList.add(singleLineSummaryEntry())
- entryList.add(
- createEntry(EntryEnum.DISABLED_PREFERENCE)
- .setHasMutableStatus(true)
- .setMacro {
- spaLogger.message(TAG, "create macro for ${EntryEnum.DISABLED_PREFERENCE}")
- SimplePreferenceMacro(
- title = DISABLE_PREFERENCE_TITLE,
- summary = DISABLE_PREFERENCE_SUMMARY,
- disabled = true,
- icon = Icons.Outlined.DisabledByDefault,
- )
- }
- .setStatusDataFn { EntryStatusData(isDisabled = true) }
- .build()
- )
- entryList.add(
- createEntry(EntryEnum.ASYNC_SUMMARY_PREFERENCE)
- .setHasMutableStatus(true)
- .setSearchDataFn {
- EntrySearchData(title = ASYNC_PREFERENCE_TITLE)
- }
- .setStatusDataFn { EntryStatusData(isDisabled = false) }
- .setUiLayoutFn {
- val model = PreferencePageModel.create()
- Preference(
- object : PreferenceModel {
- override val title = ASYNC_PREFERENCE_TITLE
- override val summary = { model.asyncSummary.value }
- override val enabled = { model.asyncEnable.value }
- }
- )
- }.build()
- )
- entryList.add(
- createEntry(EntryEnum.MANUAL_UPDATE_PREFERENCE)
- .setUiLayoutFn {
- val model = PreferencePageModel.create()
- val manualUpdaterSummary = remember { model.getManualUpdaterSummary() }
- Preference(
- object : PreferenceModel {
- override val title = MANUAL_UPDATE_PREFERENCE_TITLE
- override val summary = { manualUpdaterSummary.value }
- override val onClick = { model.manualUpdaterOnClick() }
- override val icon = @Composable {
- SettingsIcon(imageVector = Icons.Outlined.TouchApp)
- }
- }
- )
- }.build()
- )
- entryList.add(
- createEntry(EntryEnum.AUTO_UPDATE_PREFERENCE)
- .setUiLayoutFn {
- val model = PreferencePageModel.create()
- val autoUpdaterSummary = remember {
- model.getAutoUpdaterSummary()
- }.observeAsState(" ")
- Preference(
- object : PreferenceModel {
- override val title = AUTO_UPDATE_PREFERENCE_TITLE
- override val summary = { autoUpdaterSummary.value }
- override val icon = @Composable {
- SettingsIcon(imageVector = Icons.Outlined.Autorenew)
- }
- }
- )
- }.build()
- )
+ override val name = "Preference"
+ private const val PAGE_TITLE = "Sample Preference"
- return entryList
- }
+ @Composable
+ override fun Page(arguments: Bundle?) {
+ RegularScaffold(PAGE_TITLE) {
+ Category {
+ Preference(object : PreferenceModel {
+ override val title = "Preference"
+ })
+ Preference(object : PreferenceModel {
+ override val title = "Preference"
+ override val summary = { "Simple summary" }
+ })
+ val summary = stringResource(R.string.single_line_summary_preference_summary)
+ Preference(
+ model = object : PreferenceModel {
+ override val title =
+ stringResource(R.string.single_line_summary_preference_title)
+ override val summary = { summary }
+ },
+ singleLineSummary = true,
+ )
+ }
+ Category {
+ Preference(object : PreferenceModel {
+ override val title = "Disabled"
+ override val summary = { "Disabled summary" }
+ override val enabled = { false }
+ override val icon = @Composable {
+ SettingsIcon(imageVector = Icons.Outlined.DisabledByDefault)
+ }
+ })
+ }
+ Category {
+ Preference(object : PreferenceModel {
+ override val title = "Preference"
+ val asyncSummary by produceState(initialValue = " ") {
+ delay(1000L)
+ value = "Async summary"
+ }
+ override val summary = { asyncSummary }
+ })
- private fun singleLineSummaryEntry() = createEntry(EntryEnum.SINGLE_LINE_SUMMARY_PREFERENCE)
- .setUiLayoutFn {
- val summary = stringResource(R.string.single_line_summary_preference_summary)
- Preference(
- model = object : PreferenceModel {
- override val title: String =
- stringResource(R.string.single_line_summary_preference_title)
- override val summary = { summary }
- },
- singleLineSummary = true,
- )
- }
- .build()
+ var count by remember { mutableIntStateOf(0) }
+ Preference(object : PreferenceModel {
+ override val title = "Click me"
+ override val summary = { count.toString() }
+ override val onClick: (() -> Unit) = { count++ }
+ })
- fun buildInjectEntry(): SettingsEntryBuilder {
- return SettingsEntryBuilder.createInject(owner = owner)
- .setMacro {
- spaLogger.message(TAG, "create macro for INJECT entry")
- SimplePreferenceMacro(
- title = PAGE_TITLE,
- clickRoute = SettingsPageProviderEnum.PREFERENCE.name
- )
+ var ticks by remember { mutableIntStateOf(0) }
+ LaunchedEffect(ticks) {
+ delay(1000L)
+ ticks++
+ }
+ Preference(object : PreferenceModel {
+ override val title = "Ticker"
+ override val summary = { ticks.toString() }
+ })
}
+ }
}
- override fun getTitle(arguments: Bundle?): String {
- return PAGE_TITLE
+ @Composable
+ fun Entry() {
+ Preference(model = object : PreferenceModel {
+ override val title = PAGE_TITLE
+ override val onClick = navigator(name)
+ })
}
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/SwitchPreferencePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/SwitchPreferencePageProvider.kt
index f2225fa86136..9508d504a5d8 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/SwitchPreferencePageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/SwitchPreferencePageProvider.kt
@@ -27,16 +27,15 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.tooling.preview.Preview
-import com.android.settingslib.spa.framework.common.SettingsEntry
-import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
import com.android.settingslib.spa.framework.common.SettingsPageProvider
-import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spa.widget.preference.SwitchPreference
import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
+import com.android.settingslib.spa.widget.scaffold.RegularScaffold
+import com.android.settingslib.spa.widget.ui.Category
import com.android.settingslib.spa.widget.ui.SettingsIcon
import kotlinx.coroutines.delay
@@ -44,56 +43,26 @@ private const val TITLE = "Sample SwitchPreference"
object SwitchPreferencePageProvider : SettingsPageProvider {
override val name = "SwitchPreference"
- private val owner = createSettingsPage()
- override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
- val entryList = mutableListOf<SettingsEntry>()
- entryList.add(
- SettingsEntryBuilder.create( "SwitchPreference", owner)
- .setUiLayoutFn {
- SampleSwitchPreference()
- }.build()
- )
- entryList.add(
- SettingsEntryBuilder.create( "SwitchPreference with summary", owner)
- .setUiLayoutFn {
- SampleSwitchPreferenceWithSummary()
- }.build()
- )
- entryList.add(
- SettingsEntryBuilder.create( "SwitchPreference with async summary", owner)
- .setUiLayoutFn {
- SampleSwitchPreferenceWithAsyncSummary()
- }.build()
- )
- entryList.add(
- SettingsEntryBuilder.create( "SwitchPreference not changeable", owner)
- .setUiLayoutFn {
- SampleNotChangeableSwitchPreference()
- }.build()
- )
- entryList.add(
- SettingsEntryBuilder.create( "SwitchPreference with icon", owner)
- .setUiLayoutFn {
- SampleSwitchPreferenceWithIcon()
- }.build()
- )
-
- return entryList
- }
-
- fun buildInjectEntry(): SettingsEntryBuilder {
- return SettingsEntryBuilder.createInject(owner)
- .setUiLayoutFn {
- Preference(object : PreferenceModel {
- override val title = TITLE
- override val onClick = navigator(name)
- })
+ @Composable
+ override fun Page(arguments: Bundle?) {
+ RegularScaffold(TITLE) {
+ Category {
+ SampleSwitchPreference()
+ SampleSwitchPreferenceWithSummary()
+ SampleSwitchPreferenceWithAsyncSummary()
+ SampleNotChangeableSwitchPreference()
+ SampleSwitchPreferenceWithIcon()
}
+ }
}
- override fun getTitle(arguments: Bundle?): String {
- return TITLE
+ @Composable
+ fun Entry() {
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ })
}
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TopIntroPreferencePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TopIntroPreferencePageProvider.kt
index b251266e0574..ee08e30ab4ae 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TopIntroPreferencePageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TopIntroPreferencePageProvider.kt
@@ -50,15 +50,12 @@ object TopIntroPreferencePageProvider : SettingsPageProvider {
return entryList
}
- fun buildInjectEntry(): SettingsEntryBuilder {
- return SettingsEntryBuilder.createInject(owner).setUiLayoutFn {
- Preference(
- object : PreferenceModel {
- override val title = TITLE
- override val onClick = navigator(name)
- }
- )
- }
+ @Composable
+ fun Entry() {
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ })
}
override fun getTitle(arguments: Bundle?): String {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetSwitchPreferencePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetSwitchPreferencePageProvider.kt
index 19de31dab046..1a89bb2dc4f4 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetSwitchPreferencePageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetSwitchPreferencePageProvider.kt
@@ -25,66 +25,40 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.tooling.preview.Preview
-import com.android.settingslib.spa.framework.common.SettingsEntry
-import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
import com.android.settingslib.spa.framework.common.SettingsPageProvider
-import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
import com.android.settingslib.spa.widget.preference.TwoTargetSwitchPreference
+import com.android.settingslib.spa.widget.scaffold.RegularScaffold
+import com.android.settingslib.spa.widget.ui.Category
import kotlinx.coroutines.delay
private const val TITLE = "Sample TwoTargetSwitchPreference"
object TwoTargetSwitchPreferencePageProvider : SettingsPageProvider {
override val name = "TwoTargetSwitchPreference"
- private val owner = createSettingsPage()
- override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
- val entryList = mutableListOf<SettingsEntry>()
- entryList.add(
- SettingsEntryBuilder.create( "TwoTargetSwitchPreference", owner)
- .setUiLayoutFn {
- SampleTwoTargetSwitchPreference()
- }.build()
- )
- entryList.add(
- SettingsEntryBuilder.create( "TwoTargetSwitchPreference with summary", owner)
- .setUiLayoutFn {
- SampleTwoTargetSwitchPreferenceWithSummary()
- }.build()
- )
- entryList.add(
- SettingsEntryBuilder.create( "TwoTargetSwitchPreference with async summary", owner)
- .setUiLayoutFn {
- SampleTwoTargetSwitchPreferenceWithAsyncSummary()
- }.build()
- )
- entryList.add(
- SettingsEntryBuilder.create( "TwoTargetSwitchPreference not changeable", owner)
- .setUiLayoutFn {
- SampleNotChangeableTwoTargetSwitchPreference()
- }.build()
- )
-
- return entryList
- }
-
- fun buildInjectEntry(): SettingsEntryBuilder {
- return SettingsEntryBuilder.createInject(owner)
- .setUiLayoutFn {
- Preference(object : PreferenceModel {
- override val title = TITLE
- override val onClick = navigator(name)
- })
+ @Composable
+ override fun Page(arguments: Bundle?) {
+ RegularScaffold(TITLE) {
+ Category {
+ SampleTwoTargetSwitchPreference()
+ SampleTwoTargetSwitchPreferenceWithSummary()
+ SampleTwoTargetSwitchPreferenceWithAsyncSummary()
+ SampleNotChangeableTwoTargetSwitchPreference()
}
+ }
}
- override fun getTitle(arguments: Bundle?): String {
- return TITLE
+ @Composable
+ fun Entry() {
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ })
}
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/ZeroStatePreferencePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/ZeroStatePreferencePageProvider.kt
index 4a9c5c8fad4f..04b5ceb796e7 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/ZeroStatePreferencePageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/ZeroStatePreferencePageProvider.kt
@@ -53,14 +53,12 @@ object ZeroStatePreferencePageProvider : SettingsPageProvider {
return entryList
}
- fun buildInjectEntry(): SettingsEntryBuilder {
- return SettingsEntryBuilder.createInject(owner)
- .setUiLayoutFn {
- Preference(object : PreferenceModel {
- override val title = TITLE
- override val onClick = navigator(name)
- })
- }
+ @Composable
+ fun Entry() {
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ })
}
override fun getTitle(arguments: Bundle?): String {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/PagerMainPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/PagerMainPageProvider.kt
index 66cc38f74b07..c9a6557d60ef 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/PagerMainPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/PagerMainPageProvider.kt
@@ -17,7 +17,7 @@
package com.android.settingslib.spa.gallery.scaffold
import android.os.Bundle
-import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import androidx.compose.runtime.Composable
import com.android.settingslib.spa.framework.common.SettingsPageProvider
import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.navigator
@@ -34,13 +34,13 @@ object PagerMainPageProvider : SettingsPageProvider {
ScrollablePagerPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
)
- fun buildInjectEntry() = SettingsEntryBuilder.createInject(owner = owner)
- .setUiLayoutFn {
- Preference(object : PreferenceModel {
- override val title = TITLE
- override val onClick = navigator(name)
- })
- }
+ @Composable
+ fun Entry() {
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ })
+ }
override fun getTitle(arguments: Bundle?) = TITLE
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/SearchScaffoldPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/SearchScaffoldPageProvider.kt
index eac06e3eb52b..0d7cad108b7d 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/SearchScaffoldPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/SearchScaffoldPageProvider.kt
@@ -18,9 +18,7 @@ package com.android.settingslib.spa.gallery.scaffold
import android.os.Bundle
import androidx.compose.runtime.Composable
-import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
import com.android.settingslib.spa.framework.common.SettingsPageProvider
-import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
@@ -32,15 +30,13 @@ private const val TITLE = "Sample SearchScaffold"
object SearchScaffoldPageProvider : SettingsPageProvider {
override val name = "SearchScaffold"
- private val owner = createSettingsPage()
-
- fun buildInjectEntry() = SettingsEntryBuilder.createInject(owner = owner)
- .setUiLayoutFn {
- Preference(object : PreferenceModel {
- override val title = TITLE
- override val onClick = navigator(name)
- })
- }
+ @Composable
+ fun Entry() {
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ })
+ }
@Composable
override fun Page(arguments: Bundle?) {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/SuwScaffoldPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/SuwScaffoldPageProvider.kt
index a0ab2ce6945d..7b02fcb59cd8 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/SuwScaffoldPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/SuwScaffoldPageProvider.kt
@@ -27,9 +27,7 @@ import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
-import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
import com.android.settingslib.spa.framework.common.SettingsPageProvider
-import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.gallery.R
@@ -49,15 +47,13 @@ private const val TITLE = "Sample SuwScaffold"
object SuwScaffoldPageProvider : SettingsPageProvider {
override val name = "SuwScaffold"
- private val owner = createSettingsPage()
-
- fun buildInjectEntry() = SettingsEntryBuilder.createInject(owner = owner)
- .setUiLayoutFn {
- Preference(object : PreferenceModel {
- override val title = TITLE
- override val onClick = navigator(name)
- })
- }
+ @Composable
+ fun Entry() {
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ })
+ }
@Composable
override fun Page(arguments: Bundle?) {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPageProvider.kt
index 7a1fad016d0e..4d3a78a583fc 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPageProvider.kt
@@ -19,7 +19,6 @@ package com.android.settingslib.spa.gallery.ui
import android.os.Bundle
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
-import com.android.settingslib.spa.framework.common.EntrySearchData
import com.android.settingslib.spa.framework.common.SettingsEntry
import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
import com.android.settingslib.spa.framework.common.SettingsPageProvider
@@ -31,7 +30,6 @@ import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spa.widget.preference.SimplePreferenceMacro
import com.android.settingslib.spa.widget.scaffold.RegularScaffold
import com.android.settingslib.spa.widget.ui.Category
-import com.android.settingslib.spa.widget.ui.CategoryTitle
private const val TITLE = "Sample Category"
@@ -39,15 +37,14 @@ object CategoryPageProvider : SettingsPageProvider {
override val name = "Category"
private val owner = createSettingsPage()
- fun buildInjectEntry(): SettingsEntryBuilder {
- return SettingsEntryBuilder.createInject(owner)
- .setUiLayoutFn {
- Preference(object : PreferenceModel {
- override val title = TITLE
- override val onClick = navigator(name)
- })
+ @Composable
+ fun Entry() {
+ Preference(
+ object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
}
- .setSearchDataFn { EntrySearchData(title = TITLE) }
+ )
}
override fun getTitle(arguments: Bundle?): String {
@@ -70,7 +67,6 @@ object CategoryPageProvider : SettingsPageProvider {
SettingsEntryBuilder.create("Preference 3", owner)
.setMacro { SimplePreferenceMacro(title = "Preference 2", summary = "Summary 3") }
.build()
-
)
entryList.add(
SettingsEntryBuilder.create("Preference 4", owner)
@@ -84,11 +80,11 @@ object CategoryPageProvider : SettingsPageProvider {
override fun Page(arguments: Bundle?) {
val entries = buildEntry(arguments)
RegularScaffold(title = getTitle(arguments)) {
- CategoryTitle("Category A")
- entries[0].UiLayout()
- entries[1].UiLayout()
-
- Category("Category B") {
+ Category("Category A") {
+ entries[0].UiLayout()
+ entries[1].UiLayout()
+ }
+ Category {
entries[2].UiLayout()
entries[3].UiLayout()
}
@@ -99,7 +95,5 @@ object CategoryPageProvider : SettingsPageProvider {
@Preview(showBackground = true)
@Composable
private fun SpinnerPagePreview() {
- SettingsTheme {
- SpinnerPageProvider.Page(null)
- }
+ SettingsTheme { CategoryPageProvider.Page(null) }
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CopyablePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CopyablePageProvider.kt
index f897d8c58030..e919129e9dac 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CopyablePageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CopyablePageProvider.kt
@@ -21,10 +21,7 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
-import com.android.settingslib.spa.framework.common.EntrySearchData
-import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
import com.android.settingslib.spa.framework.common.SettingsPageProvider
-import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.widget.preference.Preference
@@ -37,17 +34,12 @@ private const val TITLE = "Sample Copyable"
object CopyablePageProvider : SettingsPageProvider {
override val name = "Copyable"
- private val owner = createSettingsPage()
-
- fun buildInjectEntry(): SettingsEntryBuilder {
- return SettingsEntryBuilder.createInject(owner)
- .setUiLayoutFn {
- Preference(object : PreferenceModel {
- override val title = TITLE
- override val onClick = navigator(name)
- })
- }
- .setSearchDataFn { EntrySearchData(title = TITLE) }
+ @Composable
+ fun Entry() {
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ })
}
@Composable
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/SpinnerPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/SpinnerPageProvider.kt
index 5c5c504a4310..7a4b63291375 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/SpinnerPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/SpinnerPageProvider.kt
@@ -23,9 +23,7 @@ import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.tooling.preview.Preview
-import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
import com.android.settingslib.spa.framework.common.SettingsPageProvider
-import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.widget.preference.Preference
@@ -39,14 +37,12 @@ private const val TITLE = "Sample Spinner"
object SpinnerPageProvider : SettingsPageProvider {
override val name = "Spinner"
- fun buildInjectEntry(): SettingsEntryBuilder {
- return SettingsEntryBuilder.createInject(owner = createSettingsPage())
- .setUiLayoutFn {
- Preference(object : PreferenceModel {
- override val title = TITLE
- override val onClick = navigator(name)
- })
- }
+ @Composable
+ fun Entry() {
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ })
}
override fun getTitle(arguments: Bundle?): String {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
index f8c791aab0d0..ab95162fb142 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
@@ -24,6 +24,7 @@ object SettingsDimension {
val paddingExtraSmall = 4.dp
val paddingSmall = if (isSpaExpressiveEnabled) 8.dp else 4.dp
val paddingExtraSmall5 = 10.dp
+ val paddingExtraSmall6 = 12.dp
val paddingLarge = 16.dp
val paddingExtraLarge = 24.dp
@@ -36,9 +37,9 @@ object SettingsDimension {
val itemIconSize = 24.dp
val itemIconContainerSize = 72.dp
- val itemPaddingStart = paddingExtraLarge
+ val itemPaddingStart = if (isSpaExpressiveEnabled) paddingLarge else paddingExtraLarge
val itemPaddingEnd = paddingLarge
- val itemPaddingVertical = paddingLarge
+ val itemPaddingVertical = if (isSpaExpressiveEnabled) paddingExtraSmall6 else paddingLarge
val itemPadding = PaddingValues(
start = itemPaddingStart,
top = itemPaddingVertical,
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt
index f7c5414a420c..c78771566f64 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt
@@ -24,5 +24,7 @@ object SettingsShape {
val CornerMedium = RoundedCornerShape(12.dp)
+ val categoryCorner = RoundedCornerShape(20.dp)
+
val CornerExtraLarge = RoundedCornerShape(28.dp)
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTypography.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTypography.kt
index 460bf9993b41..965c97124329 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTypography.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTypography.kt
@@ -162,3 +162,6 @@ internal fun rememberSettingsTypography(): Typography {
/** Creates a new [TextStyle] which font weight set to medium. */
internal fun TextStyle.toMediumWeight() =
copy(fontWeight = FontWeight.Medium, letterSpacing = 0.01.em)
+
+internal fun TextStyle.toSemiBoldWeight() =
+ copy(fontWeight = FontWeight.SemiBold, letterSpacing = 0.01.em)
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/banner/SettingsBanner.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/banner/SettingsBanner.kt
index 185fd2974fb1..38707b0378bc 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/banner/SettingsBanner.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/banner/SettingsBanner.kt
@@ -57,6 +57,7 @@ import com.android.settingslib.spa.framework.theme.SettingsShape.CornerExtraLarg
import com.android.settingslib.spa.framework.theme.SettingsShape.CornerExtraSmall
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.framework.theme.isSpaExpressiveEnabled
+import com.android.settingslib.spa.framework.theme.toSemiBoldWeight
import com.android.settingslib.spa.widget.ui.SettingsBody
import com.android.settingslib.spa.widget.ui.SettingsTitle
@@ -159,7 +160,9 @@ fun BannerHeader(imageVector: ImageVector?, iconColor: Color, onDismiss: (() ->
@Composable
fun BannerTitleHeader(title: String, onDismiss: (() -> Unit)? = null) {
Row(Modifier.fillMaxWidth()) {
- Box(modifier = Modifier.weight(1f)) { SettingsTitle(title) }
+ Box(modifier = Modifier.weight(1f)) {
+ Text(text = title, style = MaterialTheme.typography.titleMedium.toSemiBoldWeight())
+ }
Spacer(modifier = Modifier.padding(SettingsDimension.paddingSmall))
DismissButton(onDismiss)
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt
index 7e1df1694b10..203a8bd39fae 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt
@@ -56,6 +56,7 @@ import com.android.settingslib.spa.framework.theme.SettingsShape
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.framework.theme.divider
import com.android.settingslib.spa.framework.theme.isSpaExpressiveEnabled
+import com.android.settingslib.spa.framework.theme.toSemiBoldWeight
data class ActionButton(
val text: String,
@@ -110,7 +111,7 @@ private fun RowScope.ActionButton(actionButton: ActionButton) {
shape = RectangleShape,
colors = ButtonDefaults.filledTonalButtonColors(
containerColor = MaterialTheme.colorScheme.primaryContainer,
- contentColor = MaterialTheme.colorScheme.onPrimary,
+ contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
disabledContainerColor = MaterialTheme.colorScheme.surface,
),
contentPadding = PaddingValues(horizontal = 24.dp, vertical = 16.dp),
@@ -129,7 +130,7 @@ private fun RowScope.ActionButton(actionButton: ActionButton) {
Text(
text = actionButton.text,
textAlign = TextAlign.Center,
- style = MaterialTheme.typography.labelMedium,
+ style = MaterialTheme.typography.labelLarge.toSemiBoldWeight(),
)
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt
index 265864e1b3fd..490936fa7a47 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt
@@ -26,6 +26,7 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
+import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
@@ -99,7 +100,16 @@ private fun AlertDialogPresenter.SettingsAlertDialog(
dismissButton?.let {
{ if (isSpaExpressiveEnabled) DismissButton(it) else Button(it) }
},
- title = title?.let { { CenterRow { Text(it) } } },
+ title =
+ title?.let {
+ {
+ CenterRow {
+ if (isSpaExpressiveEnabled)
+ Text(it, style = MaterialTheme.typography.bodyLarge)
+ else Text(it)
+ }
+ }
+ },
text =
text?.let {
{ CenterRow { Column(Modifier.verticalScroll(rememberScrollState())) { text() } } }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt
index 23a8e78e6c4a..c68ec78b1ba6 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/BaseLayout.kt
@@ -16,6 +16,7 @@
package com.android.settingslib.spa.widget.preference
+import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
@@ -25,16 +26,20 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material3.HorizontalDivider
+import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.framework.theme.SettingsOpacity.alphaForEnabled
+import com.android.settingslib.spa.framework.theme.SettingsShape
import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.framework.theme.isSpaExpressiveEnabled
import com.android.settingslib.spa.widget.ui.SettingsTitle
@Composable
@@ -51,10 +56,17 @@ internal fun BaseLayout(
widget: @Composable () -> Unit = {},
) {
Row(
- modifier = modifier
- .fillMaxWidth()
- .semantics(mergeDescendants = true) {}
- .padding(end = paddingEnd),
+ modifier =
+ modifier
+ .fillMaxWidth()
+ .semantics(mergeDescendants = true) {}
+ .then(
+ if (isSpaExpressiveEnabled)
+ Modifier.clip(SettingsShape.CornerExtraSmall)
+ .background(MaterialTheme.colorScheme.surfaceBright)
+ else Modifier
+ )
+ .padding(end = paddingEnd),
verticalAlignment = Alignment.CenterVertically,
) {
val alphaModifier = Modifier.alphaForEnabled(enabled())
@@ -63,20 +75,14 @@ internal fun BaseLayout(
title = title,
titleContentDescription = titleContentDescription,
subTitle = subTitle,
- modifier = alphaModifier
- .weight(1f)
- .padding(vertical = paddingVertical),
+ modifier = alphaModifier.weight(1f).padding(vertical = paddingVertical),
)
widget()
}
}
@Composable
-internal fun BaseIcon(
- icon: @Composable (() -> Unit)?,
- modifier: Modifier,
- paddingStart: Dp,
-) {
+internal fun BaseIcon(icon: @Composable (() -> Unit)?, modifier: Modifier, paddingStart: Dp) {
if (icon != null) {
Box(
modifier = modifier.size(SettingsDimension.itemIconContainerSize),
@@ -107,11 +113,6 @@ private fun Titles(
@Composable
private fun BaseLayoutPreview() {
SettingsTheme {
- BaseLayout(
- title = "Title",
- subTitle = {
- HorizontalDivider(thickness = 10.dp)
- }
- )
+ BaseLayout(title = "Title", subTitle = { HorizontalDivider(thickness = 10.dp) })
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/IntroPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/IntroPreference.kt
index 22a57554eeaf..77073765d5a9 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/IntroPreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/IntroPreference.kt
@@ -36,6 +36,7 @@ import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.framework.theme.toSemiBoldWeight
@Composable
fun IntroPreference(
@@ -112,7 +113,7 @@ private fun IntroTitle(title: String) {
Text(
text = title,
textAlign = TextAlign.Center,
- style = MaterialTheme.typography.titleLarge,
+ style = MaterialTheme.typography.titleLarge.toSemiBoldWeight(),
color = MaterialTheme.colorScheme.onSurface,
)
}
@@ -126,7 +127,7 @@ private fun IntroDescription(descriptions: List<String>?) {
Text(
text = description,
textAlign = TextAlign.Center,
- style = MaterialTheme.typography.titleMedium,
+ style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(top = SettingsDimension.paddingExtraSmall),
)
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ZeroStatePreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ZeroStatePreference.kt
index 3f2e7723c585..b771f367e697 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ZeroStatePreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ZeroStatePreference.kt
@@ -47,6 +47,7 @@ import androidx.graphics.shapes.CornerRounding
import androidx.graphics.shapes.RoundedPolygon
import androidx.graphics.shapes.star
import androidx.graphics.shapes.toPath
+import com.android.settingslib.spa.framework.theme.toSemiBoldWeight
@Composable
fun ZeroStatePreference(icon: ImageVector, text: String? = null, description: String? = null) {
@@ -80,7 +81,7 @@ fun ZeroStatePreference(icon: ImageVector, text: String? = null, description: St
Text(
text = text,
textAlign = TextAlign.Center,
- style = MaterialTheme.typography.titleMedium,
+ style = MaterialTheme.typography.titleMedium.toSemiBoldWeight(),
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(top = 24.dp),
)
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt
index 94d2c210daab..f99d20669183 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt
@@ -41,9 +41,7 @@ import com.android.settingslib.spa.framework.theme.isSpaExpressiveEnabled
internal fun NavigateBack() {
val navController = LocalNavController.current
val contentDescription = stringResource(R.string.abc_action_bar_up_description)
- BackAction(contentDescription) {
- navController.navigateBack()
- }
+ BackAction(contentDescription) { navController.navigateBack() }
}
/** Action that collapses the search bar. */
@@ -55,15 +53,35 @@ internal fun CollapseAction(onClick: () -> Unit) {
@Composable
private fun BackAction(contentDescription: String, onClick: () -> Unit) {
- IconButton(onClick) {
+ IconButton(
+ onClick = onClick,
+ modifier =
+ if (isSpaExpressiveEnabled)
+ Modifier
+ .padding(
+ start = SettingsDimension.paddingLarge,
+ end = SettingsDimension.paddingSmall,
+ top = SettingsDimension.paddingExtraSmall,
+ bottom = SettingsDimension.paddingExtraSmall,
+ )
+ .size(SettingsDimension.actionIconWidth, SettingsDimension.actionIconHeight)
+ .clip(SettingsShape.CornerExtraLarge)
+ else Modifier,
+ ) {
Icon(
imageVector = Icons.AutoMirrored.Outlined.ArrowBack,
contentDescription = contentDescription,
- modifier = if (isSpaExpressiveEnabled) Modifier
- .size(SettingsDimension.actionIconWidth, SettingsDimension.actionIconHeight)
- .clip(SettingsShape.CornerExtraLarge)
- .background(MaterialTheme.colorScheme.surfaceContainerHigh)
- .padding(SettingsDimension.actionIconPadding) else Modifier
+ modifier =
+ if (isSpaExpressiveEnabled)
+ Modifier
+ .size(
+ SettingsDimension.actionIconWidth,
+ SettingsDimension.actionIconHeight,
+ )
+ .clip(SettingsShape.CornerExtraLarge)
+ .background(MaterialTheme.colorScheme.surfaceContainerHighest)
+ .padding(SettingsDimension.actionIconPadding)
+ else Modifier,
)
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
index 2ae3b569bc70..2c55779c9a01 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
@@ -78,7 +78,9 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.Velocity
import androidx.compose.ui.unit.dp
import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.framework.theme.isSpaExpressiveEnabled
import com.android.settingslib.spa.framework.theme.settingsBackground
+import com.android.settingslib.spa.framework.theme.toSemiBoldWeight
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.roundToInt
@@ -116,8 +118,12 @@ internal fun CustomizedLargeTopAppBar(
) {
TwoRowsTopAppBar(
title = { Title(title = title, maxLines = 3) },
- titleTextStyle = MaterialTheme.typography.displaySmall,
- smallTitleTextStyle = MaterialTheme.typography.titleMedium,
+ titleTextStyle =
+ if (isSpaExpressiveEnabled) MaterialTheme.typography.displaySmall.toSemiBoldWeight()
+ else MaterialTheme.typography.displaySmall,
+ smallTitleTextStyle =
+ if (isSpaExpressiveEnabled) MaterialTheme.typography.titleLarge.toSemiBoldWeight()
+ else MaterialTheme.typography.titleLarge,
titleBottomPadding = LargeTitleBottomPadding,
smallTitle = { Title(title = title, maxLines = 1) },
modifier = modifier,
@@ -136,7 +142,9 @@ private fun Title(title: String, maxLines: Int = Int.MAX_VALUE) {
text = title,
modifier =
Modifier.padding(
- start = SettingsDimension.itemPaddingAround,
+ start =
+ if (isSpaExpressiveEnabled) SettingsDimension.paddingExtraSmall
+ else SettingsDimension.itemPaddingAround,
end = SettingsDimension.itemPaddingEnd,
)
.semantics { heading() },
@@ -194,7 +202,7 @@ private class TopAppBarColors(
return lerp(
containerColor,
scrolledContainerColor,
- FastOutLinearInEasing.transform(colorTransitionFraction)
+ FastOutLinearInEasing.transform(colorTransitionFraction),
)
}
@@ -241,7 +249,7 @@ private fun SingleRowTopAppBar(
Row(
horizontalArrangement = Arrangement.End,
verticalAlignment = Alignment.CenterVertically,
- content = actions
+ content = actions,
)
}
@@ -296,7 +304,7 @@ private fun TwoRowsTopAppBar(
windowInsets: WindowInsets,
colors: TopAppBarColors,
pinnedHeight: Dp,
- scrollBehavior: TopAppBarScrollBehavior?
+ scrollBehavior: TopAppBarScrollBehavior?,
) {
if (MaxHeightWithoutTitle <= pinnedHeight) {
throw IllegalArgumentException(
@@ -333,7 +341,7 @@ private fun TwoRowsTopAppBar(
Row(
horizontalArrangement = Arrangement.End,
verticalAlignment = Alignment.CenterVertically,
- content = actions
+ content = actions,
)
}
val topTitleAlpha = { TopTitleAlphaEasing.transform(colorTransitionFraction()) }
@@ -356,9 +364,9 @@ private fun TwoRowsTopAppBar(
scrollBehavior.state,
velocity,
scrollBehavior.flingAnimationSpec,
- scrollBehavior.snapAnimationSpec
+ scrollBehavior.snapAnimationSpec,
)
- }
+ },
)
} else {
Modifier
@@ -412,7 +420,8 @@ private fun TwoRowsTopAppBar(
val measuredMaxHeightPx =
density.run {
MaxHeightWithoutTitle.toPx() +
- coordinates.size.height.toFloat()
+ coordinates.size.height.toFloat() +
+ titleBaselineHeight.toPx()
}
// Allow larger max height for multi-line title, but do not reduce
// max height to prevent flaky.
@@ -430,7 +439,7 @@ private fun TwoRowsTopAppBar(
titleBottomPadding = titleBottomPaddingPx,
hideTitleSemantics = hideBottomRowSemantics,
navigationIcon = {},
- actions = {}
+ actions = {},
)
}
}
@@ -485,7 +494,7 @@ private fun TopAppBarLayout(
Box(Modifier.layoutId("navigationIcon").padding(start = TopAppBarHorizontalPadding)) {
CompositionLocalProvider(
LocalContentColor provides navigationIconContentColor,
- content = navigationIcon
+ content = navigationIcon,
)
}
Box(
@@ -504,18 +513,18 @@ private fun TopAppBarLayout(
fontScale = if (titleScaleDisabled) 1f else fontScale,
)
},
- content = title
+ content = title,
)
}
}
Box(Modifier.layoutId("actionIcons").padding(end = TopAppBarHorizontalPadding)) {
CompositionLocalProvider(
LocalContentColor provides actionIconContentColor,
- content = actions
+ content = actions,
)
}
},
- modifier = modifier
+ modifier = modifier,
) { measurables, constraints ->
val navigationIconPlaceable =
measurables
@@ -552,7 +561,7 @@ private fun TopAppBarLayout(
// Navigation icon
navigationIconPlaceable.placeRelative(
x = 0,
- y = (layoutHeight - navigationIconPlaceable.height) / 2
+ y = (layoutHeight - navigationIconPlaceable.height) / 2,
)
// Title
@@ -570,17 +579,17 @@ private fun TopAppBarLayout(
titlePlaceable.height -
max(
0,
- titleBottomPadding - titlePlaceable.height + titleBaseline
+ titleBottomPadding - titlePlaceable.height + titleBaseline,
)
// Arrangement.Top
else -> 0
- }
+ },
)
// Action icons
actionIconsPlaceable.placeRelative(
x = constraints.maxWidth - actionIconsPlaceable.width,
- y = (layoutHeight - actionIconsPlaceable.height) / 2
+ y = (layoutHeight - actionIconsPlaceable.height) / 2,
)
}
}
@@ -595,7 +604,7 @@ private suspend fun settleAppBar(
state: TopAppBarState,
velocity: Float,
flingAnimationSpec: DecayAnimationSpec<Float>?,
- snapAnimationSpec: AnimationSpec<Float>?
+ snapAnimationSpec: AnimationSpec<Float>?,
): Velocity {
// Check if the app bar is completely collapsed/expanded. If so, no need to settle the app bar,
// and just return Zero Velocity.
@@ -609,20 +618,18 @@ private suspend fun settleAppBar(
// continue the motion to expand or collapse the app bar.
if (flingAnimationSpec != null && abs(velocity) > 1f) {
var lastValue = 0f
- AnimationState(
- initialValue = 0f,
- initialVelocity = velocity,
- )
- .animateDecay(flingAnimationSpec) {
- val delta = value - lastValue
- val initialHeightOffset = state.heightOffset
- state.heightOffset = initialHeightOffset + delta
- val consumed = abs(initialHeightOffset - state.heightOffset)
- lastValue = value
- remainingVelocity = this.velocity
- // avoid rounding errors and stop if anything is unconsumed
- if (abs(delta - consumed) > 0.5f) this.cancelAnimation()
- }
+ AnimationState(initialValue = 0f, initialVelocity = velocity).animateDecay(
+ flingAnimationSpec
+ ) {
+ val delta = value - lastValue
+ val initialHeightOffset = state.heightOffset
+ state.heightOffset = initialHeightOffset + delta
+ val consumed = abs(initialHeightOffset - state.heightOffset)
+ lastValue = value
+ remainingVelocity = this.velocity
+ // avoid rounding errors and stop if anything is unconsumed
+ if (abs(delta - consumed) > 0.5f) this.cancelAnimation()
+ }
}
// Snap if animation specs were provided.
if (snapAnimationSpec != null) {
@@ -633,7 +640,7 @@ private suspend fun settleAppBar(
} else {
state.heightOffsetLimit
},
- animationSpec = snapAnimationSpec
+ animationSpec = snapAnimationSpec,
) {
state.heightOffset = value
}
@@ -647,9 +654,10 @@ private suspend fun settleAppBar(
// Medium or Large app bar.
private val TopTitleAlphaEasing = CubicBezierEasing(.8f, 0f, .8f, .15f)
-internal val MaxHeightWithoutTitle = 124.dp
+internal val MaxHeightWithoutTitle = if (isSpaExpressiveEnabled) 84.dp else 124.dp
internal val DefaultTitleHeight = 52.dp
internal val ContainerHeight = 56.dp
+private val titleBaselineHeight = if (isSpaExpressiveEnabled) 8.dp else 0.dp
private val LargeTitleBottomPadding = 28.dp
private val TopAppBarHorizontalPadding = 4.dp
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Category.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Category.kt
index 48cd145da124..6c5581fb4b50 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Category.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Category.kt
@@ -16,9 +16,13 @@
package com.android.settingslib.spa.widget.ui
+import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
+import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.TouchApp
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@@ -27,25 +31,31 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.framework.theme.SettingsShape
import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.framework.theme.isSpaExpressiveEnabled
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
-/**
- * A category title that is placed before a group of similar items.
- */
+/** A category title that is placed before a group of similar items. */
@Composable
fun CategoryTitle(title: String) {
Text(
text = title,
- modifier = Modifier.padding(
- start = SettingsDimension.itemPaddingStart,
- top = 20.dp,
- end = SettingsDimension.itemPaddingEnd,
- bottom = 8.dp,
- ),
+ modifier =
+ Modifier.padding(
+ start = SettingsDimension.itemPaddingStart,
+ top = 20.dp,
+ end =
+ if (isSpaExpressiveEnabled) SettingsDimension.paddingSmall
+ else SettingsDimension.itemPaddingEnd,
+ bottom = 8.dp,
+ ),
color = MaterialTheme.colorScheme.primary,
style = MaterialTheme.typography.labelMedium,
)
@@ -56,14 +66,31 @@ fun CategoryTitle(title: String) {
* visually separates groups of items.
*/
@Composable
-fun Category(title: String, content: @Composable ColumnScope.() -> Unit) {
- Column {
+fun Category(title: String? = null, content: @Composable ColumnScope.() -> Unit) {
+ Column(
+ modifier =
+ if (isSpaExpressiveEnabled)
+ Modifier.padding(
+ horizontal = SettingsDimension.paddingLarge,
+ vertical = SettingsDimension.paddingSmall,
+ )
+ else Modifier
+ ) {
var displayTitle by remember { mutableStateOf(false) }
- if (displayTitle) CategoryTitle(title = title)
+ if (title != null && displayTitle) CategoryTitle(title = title)
Column(
- modifier = Modifier.onGloballyPositioned { coordinates ->
- displayTitle = coordinates.size.height > 0
- },
+ modifier =
+ Modifier.onGloballyPositioned { coordinates ->
+ displayTitle = coordinates.size.height > 0
+ }
+ .then(
+ if (isSpaExpressiveEnabled)
+ Modifier.fillMaxWidth().clip(SettingsShape.categoryCorner)
+ else Modifier
+ ),
+ verticalArrangement =
+ if (isSpaExpressiveEnabled) Arrangement.spacedBy(SettingsDimension.paddingTiny)
+ else Arrangement.Top,
content = content,
)
}
@@ -73,6 +100,21 @@ fun Category(title: String, content: @Composable ColumnScope.() -> Unit) {
@Composable
private fun CategoryPreview() {
SettingsTheme {
- CategoryTitle("Appearance")
+ Category("Appearance") {
+ Preference(
+ object : PreferenceModel {
+ override val title = "Title"
+ override val summary = { "Summary" }
+ }
+ )
+ Preference(
+ object : PreferenceModel {
+ override val title = "Title"
+ override val summary = { "Summary" }
+ override val icon =
+ @Composable { SettingsIcon(imageVector = Icons.Outlined.TouchApp) }
+ }
+ )
+ }
}
}
diff --git a/packages/SettingsLib/TopIntroPreference/Android.bp b/packages/SettingsLib/TopIntroPreference/Android.bp
index e70201b0feb7..76e36dc5ff7d 100644
--- a/packages/SettingsLib/TopIntroPreference/Android.bp
+++ b/packages/SettingsLib/TopIntroPreference/Android.bp
@@ -14,7 +14,10 @@ android_library {
"SettingsLintDefaults",
],
- srcs: ["src/**/*.java"],
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
resource_dirs: ["res"],
static_libs: [
diff --git a/packages/SettingsLib/TopIntroPreference/res/layout-v35/settingslib_expressive_top_intro.xml b/packages/SettingsLib/TopIntroPreference/res/layout-v35/settingslib_expressive_top_intro.xml
new file mode 100644
index 000000000000..fb13ef79cc3b
--- /dev/null
+++ b/packages/SettingsLib/TopIntroPreference/res/layout-v35/settingslib_expressive_top_intro.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart">
+
+ <com.android.settingslib.widget.CollapsableTextView
+ android:id="@+id/collapsable_text_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+</LinearLayout> \ No newline at end of file
diff --git a/packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.java b/packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.java
deleted file mode 100644
index 1bbd76d86b7f..000000000000
--- a/packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.widget;
-
-import android.content.Context;
-import android.util.AttributeSet;
-
-import androidx.preference.Preference;
-import androidx.preference.PreferenceViewHolder;
-
-import com.android.settingslib.widget.preference.topintro.R;
-
-/**
- * The TopIntroPreference shows a text which describe a feature. Gernerally, we expect this
- * preference always shows on the top of screen.
- */
-public class TopIntroPreference extends Preference {
-
- public TopIntroPreference(Context context) {
- super(context);
- setLayoutResource(R.layout.top_intro_preference);
- setSelectable(false);
- }
-
- public TopIntroPreference(Context context, AttributeSet attrs) {
- super(context, attrs);
- setLayoutResource(R.layout.top_intro_preference);
- setSelectable(false);
- }
-
- @Override
- public void onBindViewHolder(PreferenceViewHolder holder) {
- super.onBindViewHolder(holder);
- holder.setDividerAllowedAbove(false);
- holder.setDividerAllowedBelow(false);
- }
-}
diff --git a/packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.kt b/packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.kt
new file mode 100644
index 000000000000..1c5cd2aeaeb6
--- /dev/null
+++ b/packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.kt
@@ -0,0 +1,140 @@
+/*
+ * 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.widget
+
+import android.content.Context
+import android.os.Build
+import android.text.TextUtils
+import android.util.AttributeSet
+import android.view.View
+import androidx.annotation.RequiresApi
+import androidx.preference.Preference
+import androidx.preference.PreferenceViewHolder
+import com.android.settingslib.widget.preference.topintro.R
+
+open class TopIntroPreference @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0,
+ defStyleRes: Int = 0
+) : Preference(context, attrs, defStyleAttr, defStyleRes) {
+
+ private var isCollapsable: Boolean = false
+ private var minLines: Int = 2
+ private var learnMoreListener: View.OnClickListener? = null
+ private var learnMoreText: CharSequence? = null
+
+ init {
+ if (SettingsThemeHelper.isExpressiveTheme(context)) {
+ layoutResource = R.layout.settingslib_expressive_top_intro
+ initAttributes(context, attrs, defStyleAttr)
+ } else {
+ layoutResource = R.layout.top_intro_preference
+ }
+ isSelectable = false
+ }
+
+ private fun initAttributes(context: Context, attrs: AttributeSet?, defStyleAttr: Int) {
+ context.obtainStyledAttributes(
+ attrs,
+ COLLAPSABLE_TEXT_VIEW_ATTRS, defStyleAttr, 0
+ ).apply {
+ isCollapsable = getBoolean(IS_COLLAPSABLE, false)
+ minLines = getInt(
+ MIN_LINES,
+ if (isCollapsable) DEFAULT_MIN_LINES else DEFAULT_MAX_LINES
+ ).coerceIn(1, DEFAULT_MAX_LINES)
+ recycle()
+ }
+ }
+
+ override fun onBindViewHolder(holder: PreferenceViewHolder) {
+ super.onBindViewHolder(holder)
+ holder.isDividerAllowedAbove = false
+ holder.isDividerAllowedBelow = false
+
+ if (!SettingsThemeHelper.isExpressiveTheme(context)) {
+ return
+ }
+
+ (holder.findViewById(R.id.collapsable_text_view) as? CollapsableTextView)?.apply {
+ setCollapsable(isCollapsable)
+ setMinLines(minLines)
+ setText(title.toString())
+ if (learnMoreListener != null) {
+ setLearnMoreText(learnMoreText)
+ setLearnMoreAction(learnMoreListener)
+ }
+ }
+ }
+
+ /**
+ * Sets whether the text view is collapsable.
+ * @param collapsable True if the text view should be collapsable, false otherwise.
+ */
+ @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ fun setCollapsable(collapsable: Boolean) {
+ isCollapsable = collapsable
+ notifyChanged()
+ }
+
+ /**
+ * Sets the minimum number of lines to display when collapsed.
+ * @param lines The minimum number of lines.
+ */
+ @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ fun setMinLines(lines: Int) {
+ minLines = lines.coerceIn(1, DEFAULT_MAX_LINES)
+ notifyChanged()
+ }
+
+ /**
+ * Sets the action when clicking on the learn more view.
+ * @param listener The click listener for learn more.
+ */
+ @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ fun setLearnMoreAction(listener: View.OnClickListener) {
+ if (learnMoreListener != listener) {
+ learnMoreListener = listener
+ notifyChanged()
+ }
+ }
+
+ /**
+ * Sets the text of learn more view.
+ * @param text The text of learn more.
+ */
+ @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ fun setLearnMoreText(text: CharSequence) {
+ if (!TextUtils.equals(learnMoreText, text)) {
+ learnMoreText = text
+ notifyChanged()
+ }
+ }
+
+ companion object {
+ private const val DEFAULT_MAX_LINES = 10
+ private const val DEFAULT_MIN_LINES = 2
+
+ private val COLLAPSABLE_TEXT_VIEW_ATTRS =
+ com.android.settingslib.widget.theme.R.styleable.CollapsableTextView
+ private val MIN_LINES =
+ com.android.settingslib.widget.theme.R.styleable.CollapsableTextView_android_minLines
+ private val IS_COLLAPSABLE =
+ com.android.settingslib.widget.theme.R.styleable.CollapsableTextView_isCollapsable
+ }
+}
diff --git a/packages/SettingsLib/res/values-af/strings.xml b/packages/SettingsLib/res/values-af/strings.xml
index de728e27beac..0a507d12a5cb 100644
--- a/packages/SettingsLib/res/values-af/strings.xml
+++ b/packages/SettingsLib/res/values-af/strings.xml
@@ -582,8 +582,7 @@
<string name="time_unit_just_now" msgid="3006134267292728099">"Sopas"</string>
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Hierdie foon"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Hierdie tablet"</string>
- <!-- no translation found for media_transfer_this_device_name_desktop (7912386128141470452) -->
- <skip />
+ <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Hierdie rekenaar (intern)"</string>
<!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
<skip />
<string name="media_transfer_internal_mic" msgid="797333824290228595">"Mikrofoon (intern)"</string>
@@ -687,12 +686,9 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Gedeaktiveer"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Geaktiveer"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Jou toestel moet herselflaai om hierdie verandering toe te pas. Herselflaai nou of kanselleer."</string>
- <!-- no translation found for media_transfer_wired_headphone_name (8698668536022665254) -->
- <skip />
- <!-- no translation found for media_transfer_headphone_name (1131962659136578852) -->
- <skip />
- <!-- no translation found for media_transfer_usb_speaker_name (4736537022543593896) -->
- <skip />
+ <string name="media_transfer_wired_headphone_name" msgid="8698668536022665254">"Bedrade oorfoon"</string>
+ <string name="media_transfer_headphone_name" msgid="1131962659136578852">"Oorfoon"</string>
+ <string name="media_transfer_usb_speaker_name" msgid="4736537022543593896">"USB-luidspreker"</string>
<string name="media_transfer_wired_device_mic_name" msgid="7417067197803840965">"Mikrofoonsok"</string>
<string name="media_transfer_usb_device_mic_name" msgid="9189914846215516322">"USB-mikrofoon"</string>
<string name="wifi_hotspot_switch_on_text" msgid="9212273118217786155">"Aan"</string>
diff --git a/packages/SettingsLib/res/values-az/strings.xml b/packages/SettingsLib/res/values-az/strings.xml
index 4715109b8f45..057d49249cc7 100644
--- a/packages/SettingsLib/res/values-az/strings.xml
+++ b/packages/SettingsLib/res/values-az/strings.xml
@@ -582,8 +582,7 @@
<string name="time_unit_just_now" msgid="3006134267292728099">"İndicə"</string>
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Bu telefon"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Bu planşet"</string>
- <!-- no translation found for media_transfer_this_device_name_desktop (7912386128141470452) -->
- <skip />
+ <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Bu kompüter (daxili)"</string>
<!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
<skip />
<string name="media_transfer_internal_mic" msgid="797333824290228595">"Mikrofon (daxili)"</string>
@@ -687,12 +686,9 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Deaktiv"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Aktiv"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Bu dəyişikliyin tətbiq edilməsi üçün cihaz yenidən başladılmalıdır. İndi yenidən başladın və ya ləğv edin."</string>
- <!-- no translation found for media_transfer_wired_headphone_name (8698668536022665254) -->
- <skip />
- <!-- no translation found for media_transfer_headphone_name (1131962659136578852) -->
- <skip />
- <!-- no translation found for media_transfer_usb_speaker_name (4736537022543593896) -->
- <skip />
+ <string name="media_transfer_wired_headphone_name" msgid="8698668536022665254">"Naqilli qulaqlıq"</string>
+ <string name="media_transfer_headphone_name" msgid="1131962659136578852">"Qulaqlıq"</string>
+ <string name="media_transfer_usb_speaker_name" msgid="4736537022543593896">"USB spikeri"</string>
<string name="media_transfer_wired_device_mic_name" msgid="7417067197803840965">"Mikrofon yuvası"</string>
<string name="media_transfer_usb_device_mic_name" msgid="9189914846215516322">"USB mikrofon"</string>
<string name="wifi_hotspot_switch_on_text" msgid="9212273118217786155">"Aktiv"</string>
diff --git a/packages/SettingsLib/res/values-be/strings.xml b/packages/SettingsLib/res/values-be/strings.xml
index 2adcc43a885b..a262901b7992 100644
--- a/packages/SettingsLib/res/values-be/strings.xml
+++ b/packages/SettingsLib/res/values-be/strings.xml
@@ -582,8 +582,7 @@
<string name="time_unit_just_now" msgid="3006134267292728099">"Толькі што"</string>
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Гэты тэлефон"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Гэты планшэт"</string>
- <!-- no translation found for media_transfer_this_device_name_desktop (7912386128141470452) -->
- <skip />
+ <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Гэты камп’ютар (унутраны)"</string>
<!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
<skip />
<string name="media_transfer_internal_mic" msgid="797333824290228595">"Мікрафон (унутраны)"</string>
@@ -687,12 +686,9 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Выключана"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Уключана"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Перазагрузіце прыладу, каб прымяніць гэта змяненне. Перазагрузіце ці скасуйце."</string>
- <!-- no translation found for media_transfer_wired_headphone_name (8698668536022665254) -->
- <skip />
- <!-- no translation found for media_transfer_headphone_name (1131962659136578852) -->
- <skip />
- <!-- no translation found for media_transfer_usb_speaker_name (4736537022543593896) -->
- <skip />
+ <string name="media_transfer_wired_headphone_name" msgid="8698668536022665254">"Правадныя навушнікі"</string>
+ <string name="media_transfer_headphone_name" msgid="1131962659136578852">"Навушнікі"</string>
+ <string name="media_transfer_usb_speaker_name" msgid="4736537022543593896">"USB-дынамік"</string>
<string name="media_transfer_wired_device_mic_name" msgid="7417067197803840965">"Раздым для мікрафона"</string>
<string name="media_transfer_usb_device_mic_name" msgid="9189914846215516322">"Мікрафон USB"</string>
<string name="wifi_hotspot_switch_on_text" msgid="9212273118217786155">"Уключана"</string>
diff --git a/packages/SettingsLib/res/values-bg/strings.xml b/packages/SettingsLib/res/values-bg/strings.xml
index df5051f175a8..244a090aa594 100644
--- a/packages/SettingsLib/res/values-bg/strings.xml
+++ b/packages/SettingsLib/res/values-bg/strings.xml
@@ -582,8 +582,7 @@
<string name="time_unit_just_now" msgid="3006134267292728099">"Току-що"</string>
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Този телефон"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Този таблет"</string>
- <!-- no translation found for media_transfer_this_device_name_desktop (7912386128141470452) -->
- <skip />
+ <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Този компютър (вграден)"</string>
<!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
<skip />
<string name="media_transfer_internal_mic" msgid="797333824290228595">"Микрофон (вътрешен)"</string>
@@ -687,12 +686,9 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Деактивирано"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Активирано"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"За да бъде приложена тази промяна, устройството ви трябва да бъде рестартирано. Рестартирайте сега или анулирайте."</string>
- <!-- no translation found for media_transfer_wired_headphone_name (8698668536022665254) -->
- <skip />
- <!-- no translation found for media_transfer_headphone_name (1131962659136578852) -->
- <skip />
- <!-- no translation found for media_transfer_usb_speaker_name (4736537022543593896) -->
- <skip />
+ <string name="media_transfer_wired_headphone_name" msgid="8698668536022665254">"Слушалки с кабел"</string>
+ <string name="media_transfer_headphone_name" msgid="1131962659136578852">"Слушалки"</string>
+ <string name="media_transfer_usb_speaker_name" msgid="4736537022543593896">"Високоговорител с USB"</string>
<string name="media_transfer_wired_device_mic_name" msgid="7417067197803840965">"Жак за микрофон"</string>
<string name="media_transfer_usb_device_mic_name" msgid="9189914846215516322">"Микрофон с USB"</string>
<string name="wifi_hotspot_switch_on_text" msgid="9212273118217786155">"Включване"</string>
diff --git a/packages/SettingsLib/res/values-bn/strings.xml b/packages/SettingsLib/res/values-bn/strings.xml
index e7b97236a246..907cee10d23f 100644
--- a/packages/SettingsLib/res/values-bn/strings.xml
+++ b/packages/SettingsLib/res/values-bn/strings.xml
@@ -582,8 +582,7 @@
<string name="time_unit_just_now" msgid="3006134267292728099">"এখনই"</string>
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"এই ফোন"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"এই ট্যাবলেট"</string>
- <!-- no translation found for media_transfer_this_device_name_desktop (7912386128141470452) -->
- <skip />
+ <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"এই কম্পিউটার (ইন্টার্নাল)"</string>
<!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
<skip />
<string name="media_transfer_internal_mic" msgid="797333824290228595">"মাইক্রোফোন (ইন্টার্নাল)"</string>
@@ -687,12 +686,9 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"বন্ধ করা আছে"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"চালু করা আছে"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"এই পরিবর্তনটি প্রয়োগ করার জন্য আপনার ডিভাইসটি অবশ্যই রিবুট করতে হবে। এখনই রিবুট করুন বা বাতিল করুন।"</string>
- <!-- no translation found for media_transfer_wired_headphone_name (8698668536022665254) -->
- <skip />
- <!-- no translation found for media_transfer_headphone_name (1131962659136578852) -->
- <skip />
- <!-- no translation found for media_transfer_usb_speaker_name (4736537022543593896) -->
- <skip />
+ <string name="media_transfer_wired_headphone_name" msgid="8698668536022665254">"তারযুক্ত হেডফোন"</string>
+ <string name="media_transfer_headphone_name" msgid="1131962659136578852">"হেডফোন"</string>
+ <string name="media_transfer_usb_speaker_name" msgid="4736537022543593896">"USB স্পিকার"</string>
<string name="media_transfer_wired_device_mic_name" msgid="7417067197803840965">"মাইকের জ্যাক"</string>
<string name="media_transfer_usb_device_mic_name" msgid="9189914846215516322">"USB মাইক"</string>
<string name="wifi_hotspot_switch_on_text" msgid="9212273118217786155">"চালু আছে"</string>
diff --git a/packages/SettingsLib/res/values-bs/strings.xml b/packages/SettingsLib/res/values-bs/strings.xml
index e8d4a7c807cc..6b09b51a2f68 100644
--- a/packages/SettingsLib/res/values-bs/strings.xml
+++ b/packages/SettingsLib/res/values-bs/strings.xml
@@ -582,7 +582,7 @@
<string name="time_unit_just_now" msgid="3006134267292728099">"Upravo"</string>
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Ovaj telefon"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Ovaj tablet"</string>
- <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Ovo računalo (interno)"</string>
+ <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Ovaj računar (interno)"</string>
<!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
<skip />
<string name="media_transfer_internal_mic" msgid="797333824290228595">"Mikrofon (interni)"</string>
diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml
index d75e3142a8f9..305772989db9 100644
--- a/packages/SettingsLib/res/values-ca/strings.xml
+++ b/packages/SettingsLib/res/values-ca/strings.xml
@@ -692,7 +692,7 @@
<string name="media_transfer_wired_device_mic_name" msgid="7417067197803840965">"Connector per al micròfon"</string>
<string name="media_transfer_usb_device_mic_name" msgid="9189914846215516322">"Micròfon USB"</string>
<string name="wifi_hotspot_switch_on_text" msgid="9212273118217786155">"Activa"</string>
- <string name="wifi_hotspot_switch_off_text" msgid="7245567251496959764">"Desactiva"</string>
+ <string name="wifi_hotspot_switch_off_text" msgid="7245567251496959764">"Desactivat"</string>
<string name="carrier_network_change_mode" msgid="4257621815706644026">"S\'està canviant la xarxa de l\'operador de telefonia mòbil"</string>
<string name="data_connection_3g" msgid="931852552688157407">"3G"</string>
<string name="data_connection_edge" msgid="4625509456544797637">"EDGE"</string>
diff --git a/packages/SettingsLib/res/values-cs/strings.xml b/packages/SettingsLib/res/values-cs/strings.xml
index e77505017842..d274e850c0f0 100644
--- a/packages/SettingsLib/res/values-cs/strings.xml
+++ b/packages/SettingsLib/res/values-cs/strings.xml
@@ -582,8 +582,7 @@
<string name="time_unit_just_now" msgid="3006134267292728099">"Právě teď"</string>
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Tento telefon"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Tento tablet"</string>
- <!-- no translation found for media_transfer_this_device_name_desktop (7912386128141470452) -->
- <skip />
+ <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Tento počítač (interní)"</string>
<!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
<skip />
<string name="media_transfer_internal_mic" msgid="797333824290228595">"Mikrofon (interní)"</string>
@@ -687,12 +686,9 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Vypnuto"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Zapnuto"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Aby se tato změna projevila, je třeba zařízení restartovat. Restartujte zařízení nebo zrušte akci."</string>
- <!-- no translation found for media_transfer_wired_headphone_name (8698668536022665254) -->
- <skip />
- <!-- no translation found for media_transfer_headphone_name (1131962659136578852) -->
- <skip />
- <!-- no translation found for media_transfer_usb_speaker_name (4736537022543593896) -->
- <skip />
+ <string name="media_transfer_wired_headphone_name" msgid="8698668536022665254">"Kabelová sluchátka"</string>
+ <string name="media_transfer_headphone_name" msgid="1131962659136578852">"Sluchátka"</string>
+ <string name="media_transfer_usb_speaker_name" msgid="4736537022543593896">"USB reproduktor"</string>
<string name="media_transfer_wired_device_mic_name" msgid="7417067197803840965">"Konektor mikrofonu"</string>
<string name="media_transfer_usb_device_mic_name" msgid="9189914846215516322">"USB mikrofon"</string>
<string name="wifi_hotspot_switch_on_text" msgid="9212273118217786155">"Zapnout"</string>
diff --git a/packages/SettingsLib/res/values-da/strings.xml b/packages/SettingsLib/res/values-da/strings.xml
index a85bc291afa2..ef37c658ffeb 100644
--- a/packages/SettingsLib/res/values-da/strings.xml
+++ b/packages/SettingsLib/res/values-da/strings.xml
@@ -582,8 +582,7 @@
<string name="time_unit_just_now" msgid="3006134267292728099">"Lige nu"</string>
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Denne telefon"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Denne tablet"</string>
- <!-- no translation found for media_transfer_this_device_name_desktop (7912386128141470452) -->
- <skip />
+ <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Denne computer (intern)"</string>
<!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
<skip />
<string name="media_transfer_internal_mic" msgid="797333824290228595">"Mikrofon (indbygget)"</string>
@@ -687,12 +686,9 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Deaktiveret"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Aktiveret"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Din enhed skal genstartes for at anvende denne ændring. Genstart nu, eller annuller."</string>
- <!-- no translation found for media_transfer_wired_headphone_name (8698668536022665254) -->
- <skip />
- <!-- no translation found for media_transfer_headphone_name (1131962659136578852) -->
- <skip />
- <!-- no translation found for media_transfer_usb_speaker_name (4736537022543593896) -->
- <skip />
+ <string name="media_transfer_wired_headphone_name" msgid="8698668536022665254">"Høretelefoner med ledning"</string>
+ <string name="media_transfer_headphone_name" msgid="1131962659136578852">"Høretelefoner"</string>
+ <string name="media_transfer_usb_speaker_name" msgid="4736537022543593896">"USB-højttaler"</string>
<string name="media_transfer_wired_device_mic_name" msgid="7417067197803840965">"Stik til mikrofon"</string>
<string name="media_transfer_usb_device_mic_name" msgid="9189914846215516322">"USB-mikrofon"</string>
<string name="wifi_hotspot_switch_on_text" msgid="9212273118217786155">"Til"</string>
diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml
index 9ba5b50982bf..44f7329bc8db 100644
--- a/packages/SettingsLib/res/values-de/strings.xml
+++ b/packages/SettingsLib/res/values-de/strings.xml
@@ -582,8 +582,7 @@
<string name="time_unit_just_now" msgid="3006134267292728099">"Gerade eben"</string>
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Dieses Smartphone"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Dieses Tablet"</string>
- <!-- no translation found for media_transfer_this_device_name_desktop (7912386128141470452) -->
- <skip />
+ <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Dieser Computer (intern)"</string>
<!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
<skip />
<string name="media_transfer_internal_mic" msgid="797333824290228595">"Mikrofon (intern)"</string>
@@ -687,12 +686,9 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Deaktiviert"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Aktiviert"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Damit diese Änderung übernommen wird, musst du dein Gerät neu starten. Du kannst es jetzt neu starten oder den Vorgang abbrechen."</string>
- <!-- no translation found for media_transfer_wired_headphone_name (8698668536022665254) -->
- <skip />
- <!-- no translation found for media_transfer_headphone_name (1131962659136578852) -->
- <skip />
- <!-- no translation found for media_transfer_usb_speaker_name (4736537022543593896) -->
- <skip />
+ <string name="media_transfer_wired_headphone_name" msgid="8698668536022665254">"Kabelgebundene Kopfhörer"</string>
+ <string name="media_transfer_headphone_name" msgid="1131962659136578852">"Kopfhörer"</string>
+ <string name="media_transfer_usb_speaker_name" msgid="4736537022543593896">"USB-Lautsprecher"</string>
<string name="media_transfer_wired_device_mic_name" msgid="7417067197803840965">"Mikrofonanschluss"</string>
<string name="media_transfer_usb_device_mic_name" msgid="9189914846215516322">"USB‑Mikrofon"</string>
<string name="wifi_hotspot_switch_on_text" msgid="9212273118217786155">"An"</string>
diff --git a/packages/SettingsLib/res/values-el/strings.xml b/packages/SettingsLib/res/values-el/strings.xml
index ce045a9cb275..9cfceb56751c 100644
--- a/packages/SettingsLib/res/values-el/strings.xml
+++ b/packages/SettingsLib/res/values-el/strings.xml
@@ -287,7 +287,7 @@
<string name="oem_unlock_enable_summary" msgid="5857388174390953829">"Να επιτρέπεται το ξεκλείδωμα λειτουργίας εκκίνησης"</string>
<string name="confirm_enable_oem_unlock_title" msgid="8249318129774367535">"Να επιτρέπεται το ξεκλείδωμα OEM;"</string>
<string name="confirm_enable_oem_unlock_text" msgid="854131050791011970">"ΠΡΟΕΙΔΟΠΟΙΗΣΗ: Οι λειτουργίες προστασίας συσκευής δεν θα λειτουργούν σε αυτήν τη συσκευή, όταν είναι ενεργοποιημένη αυτή η ρύθμιση."</string>
- <string name="mock_location_app" msgid="6269380172542248304">"Επιλογή εφ/γής τεχνητής τοπ/σίας"</string>
+ <string name="mock_location_app" msgid="6269380172542248304">"Επιλογή εφαρμ. τεχνητής τοποθ."</string>
<string name="mock_location_app_not_set" msgid="6972032787262831155">"Δεν ορίστηκε εφαρμογή τεχνητής τοποθεσίας"</string>
<string name="mock_location_app_set" msgid="4706722469342913843">"Εφαρμογή τεχνητής τοποθεσίας: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="debug_networking_category" msgid="6829757985772659599">"Δικτύωση"</string>
@@ -329,7 +329,7 @@
<string name="wifi_non_persistent_mac_randomization_summary" msgid="2159794543105053930">"Όταν ενεργοποιηθεί αυτή η λειτουργία, η διεύθυνση MAC αυτής της συσκευής μπορεί να αλλάζει κάθε φορά που συνδέεται σε ένα δίκτυο όπου έχει ενεργοποιηθεί η τυχαία σειρά διευθύνσεων MAC."</string>
<string name="wifi_metered_label" msgid="8737187690304098638">"Μέτρηση με βάση τη χρήση"</string>
<string name="wifi_unmetered_label" msgid="6174142840934095093">"Χωρίς μέτρηση με βάση τη χρήση"</string>
- <string name="select_logd_size_title" msgid="1604578195914595173">"Μέγεθος προσωρινής μνήμης για τη λειτουργία καταγραφής"</string>
+ <string name="select_logd_size_title" msgid="1604578195914595173">"Μέγεθος προσωρινής μνήμης για τη λειτ. καταγραφής"</string>
<string name="select_logd_size_dialog_title" msgid="2105401994681013578">"Μέγεθος αρχείων κατ/φής ανά προ/νή μνήμη αρχείου κατ/φής"</string>
<string name="dev_logpersist_clear_warning_title" msgid="8631859265777337991">"Εκκαθάριση αποθηκευτικού χώρου μόνιμων αρχείων καταγραφής;"</string>
<string name="dev_logpersist_clear_warning_message" msgid="6447590867594287413">"Όταν δεν γίνεται πλέον παρακολούθηση με μόνιμο αρχείο καταγραφής, θα πρέπει να διαγραφούν τα δεδομένα του αρχείου καταγραφής στη συσκευή σας."</string>
@@ -582,8 +582,7 @@
<string name="time_unit_just_now" msgid="3006134267292728099">"Μόλις τώρα"</string>
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Αυτό το τηλέφωνο"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Αυτό το tablet"</string>
- <!-- no translation found for media_transfer_this_device_name_desktop (7912386128141470452) -->
- <skip />
+ <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Αυτός ο υπολογιστής (εσωτερ.)"</string>
<!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
<skip />
<string name="media_transfer_internal_mic" msgid="797333824290228595">"Μικρόφωνο (εσωτερικό)"</string>
@@ -687,12 +686,9 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Ανενεργή"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Ενεργή"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Για να εφαρμοστεί αυτή η αλλαγή, θα πρέπει να επανεκκινήσετε τη συσκευή σας. Επανεκκίνηση τώρα ή ακύρωση."</string>
- <!-- no translation found for media_transfer_wired_headphone_name (8698668536022665254) -->
- <skip />
- <!-- no translation found for media_transfer_headphone_name (1131962659136578852) -->
- <skip />
- <!-- no translation found for media_transfer_usb_speaker_name (4736537022543593896) -->
- <skip />
+ <string name="media_transfer_wired_headphone_name" msgid="8698668536022665254">"Ενσύρματα ακουστικά"</string>
+ <string name="media_transfer_headphone_name" msgid="1131962659136578852">"Ακουστικά"</string>
+ <string name="media_transfer_usb_speaker_name" msgid="4736537022543593896">"Ηχείο USB"</string>
<string name="media_transfer_wired_device_mic_name" msgid="7417067197803840965">"Υποδοχή μικροφώνου"</string>
<string name="media_transfer_usb_device_mic_name" msgid="9189914846215516322">"Μικρόφωνο USB"</string>
<string name="wifi_hotspot_switch_on_text" msgid="9212273118217786155">"Ενεργό"</string>
diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml
index b0d360d582f6..d6222ac07660 100644
--- a/packages/SettingsLib/res/values-es-rUS/strings.xml
+++ b/packages/SettingsLib/res/values-es-rUS/strings.xml
@@ -582,8 +582,7 @@
<string name="time_unit_just_now" msgid="3006134267292728099">"Recién"</string>
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Este teléfono"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Esta tablet"</string>
- <!-- no translation found for media_transfer_this_device_name_desktop (7912386128141470452) -->
- <skip />
+ <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Esta computadora (interna)"</string>
<!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
<skip />
<string name="media_transfer_internal_mic" msgid="797333824290228595">"Micrófono (interno)"</string>
@@ -687,12 +686,9 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Inhabilitado"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Habilitado"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Debes reiniciar el dispositivo para que se aplique el cambio. Reinícialo ahora o cancela la acción."</string>
- <!-- no translation found for media_transfer_wired_headphone_name (8698668536022665254) -->
- <skip />
- <!-- no translation found for media_transfer_headphone_name (1131962659136578852) -->
- <skip />
- <!-- no translation found for media_transfer_usb_speaker_name (4736537022543593896) -->
- <skip />
+ <string name="media_transfer_wired_headphone_name" msgid="8698668536022665254">"Auriculares con cable"</string>
+ <string name="media_transfer_headphone_name" msgid="1131962659136578852">"Auriculares"</string>
+ <string name="media_transfer_usb_speaker_name" msgid="4736537022543593896">"Bocina USB"</string>
<string name="media_transfer_wired_device_mic_name" msgid="7417067197803840965">"Conector para micrófono"</string>
<string name="media_transfer_usb_device_mic_name" msgid="9189914846215516322">"Micrófono USB"</string>
<string name="wifi_hotspot_switch_on_text" msgid="9212273118217786155">"Activar"</string>
diff --git a/packages/SettingsLib/res/values-es/strings.xml b/packages/SettingsLib/res/values-es/strings.xml
index 716713a6fb6a..d88634e2e72e 100644
--- a/packages/SettingsLib/res/values-es/strings.xml
+++ b/packages/SettingsLib/res/values-es/strings.xml
@@ -582,8 +582,7 @@
<string name="time_unit_just_now" msgid="3006134267292728099">"justo ahora"</string>
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Este teléfono"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Esta tablet"</string>
- <!-- no translation found for media_transfer_this_device_name_desktop (7912386128141470452) -->
- <skip />
+ <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Este ordenador (interno)"</string>
<!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
<skip />
<string name="media_transfer_internal_mic" msgid="797333824290228595">"Micrófono (interno)"</string>
@@ -687,12 +686,9 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Inhabilitado"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Habilitado"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Es necesario reiniciar tu dispositivo para que se apliquen los cambios. Reinicia ahora o cancela la acción."</string>
- <!-- no translation found for media_transfer_wired_headphone_name (8698668536022665254) -->
- <skip />
- <!-- no translation found for media_transfer_headphone_name (1131962659136578852) -->
- <skip />
- <!-- no translation found for media_transfer_usb_speaker_name (4736537022543593896) -->
- <skip />
+ <string name="media_transfer_wired_headphone_name" msgid="8698668536022665254">"Auriculares con cable"</string>
+ <string name="media_transfer_headphone_name" msgid="1131962659136578852">"Auriculares"</string>
+ <string name="media_transfer_usb_speaker_name" msgid="4736537022543593896">"Altavoz USB"</string>
<string name="media_transfer_wired_device_mic_name" msgid="7417067197803840965">"Conector jack para micrófono"</string>
<string name="media_transfer_usb_device_mic_name" msgid="9189914846215516322">"Micrófono USB"</string>
<string name="wifi_hotspot_switch_on_text" msgid="9212273118217786155">"Activado"</string>
diff --git a/packages/SettingsLib/res/values-et/strings.xml b/packages/SettingsLib/res/values-et/strings.xml
index f1975fa4c402..e3020be848ab 100644
--- a/packages/SettingsLib/res/values-et/strings.xml
+++ b/packages/SettingsLib/res/values-et/strings.xml
@@ -582,8 +582,7 @@
<string name="time_unit_just_now" msgid="3006134267292728099">"Äsja"</string>
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"See telefon"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"See tahvelarvuti"</string>
- <!-- no translation found for media_transfer_this_device_name_desktop (7912386128141470452) -->
- <skip />
+ <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"See arvuti (sisemine)"</string>
<!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
<skip />
<string name="media_transfer_internal_mic" msgid="797333824290228595">"Mikrofon (sisemine)"</string>
@@ -687,12 +686,9 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Keelatud"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Lubatud"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Selle muudatuse rakendamiseks tuleb seade taaskäivitada. Taaskäivitage kohe või tühistage."</string>
- <!-- no translation found for media_transfer_wired_headphone_name (8698668536022665254) -->
- <skip />
- <!-- no translation found for media_transfer_headphone_name (1131962659136578852) -->
- <skip />
- <!-- no translation found for media_transfer_usb_speaker_name (4736537022543593896) -->
- <skip />
+ <string name="media_transfer_wired_headphone_name" msgid="8698668536022665254">"Juhtmega kõrvaklapid"</string>
+ <string name="media_transfer_headphone_name" msgid="1131962659136578852">"Kõrvaklapid"</string>
+ <string name="media_transfer_usb_speaker_name" msgid="4736537022543593896">"USB kõlar"</string>
<string name="media_transfer_wired_device_mic_name" msgid="7417067197803840965">"Mikrofoni pistikupesa"</string>
<string name="media_transfer_usb_device_mic_name" msgid="9189914846215516322">"USB-mikrofon"</string>
<string name="wifi_hotspot_switch_on_text" msgid="9212273118217786155">"Sees"</string>
diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml
index fc4157c5e560..da47004793bc 100644
--- a/packages/SettingsLib/res/values-eu/strings.xml
+++ b/packages/SettingsLib/res/values-eu/strings.xml
@@ -582,8 +582,7 @@
<string name="time_unit_just_now" msgid="3006134267292728099">"Oraintxe"</string>
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Telefono hau"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Tableta hau"</string>
- <!-- no translation found for media_transfer_this_device_name_desktop (7912386128141470452) -->
- <skip />
+ <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Ordenagailu hau (barnekoa)"</string>
<!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
<skip />
<string name="media_transfer_internal_mic" msgid="797333824290228595">"Mikrofonoa (barnekoa)"</string>
@@ -687,12 +686,9 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Desgaituta"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Gaituta"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Aldaketa aplikatzeko, berrabiarazi egin behar da gailua. Berrabiaraz ezazu orain, edo utzi bertan behera."</string>
- <!-- no translation found for media_transfer_wired_headphone_name (8698668536022665254) -->
- <skip />
- <!-- no translation found for media_transfer_headphone_name (1131962659136578852) -->
- <skip />
- <!-- no translation found for media_transfer_usb_speaker_name (4736537022543593896) -->
- <skip />
+ <string name="media_transfer_wired_headphone_name" msgid="8698668536022665254">"Entzungailu kableduna"</string>
+ <string name="media_transfer_headphone_name" msgid="1131962659136578852">"Entzungailua"</string>
+ <string name="media_transfer_usb_speaker_name" msgid="4736537022543593896">"USB bidezko bozgorailua"</string>
<string name="media_transfer_wired_device_mic_name" msgid="7417067197803840965">"Mikrofonoaren konektorea"</string>
<string name="media_transfer_usb_device_mic_name" msgid="9189914846215516322">"USB bidezko mikrofonoa"</string>
<string name="wifi_hotspot_switch_on_text" msgid="9212273118217786155">"Aktibatu"</string>
diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml
index 3dc0e8573a95..5996efb9a9a7 100644
--- a/packages/SettingsLib/res/values-fa/strings.xml
+++ b/packages/SettingsLib/res/values-fa/strings.xml
@@ -239,10 +239,10 @@
<string name="category_work" msgid="4014193632325996115">"کاری"</string>
<string name="category_private" msgid="4244892185452788977">"خصوصی"</string>
<string name="category_clone" msgid="1554511758987195974">"همسانه‌سازی"</string>
- <string name="development_settings_title" msgid="140296922921597393">"گزینه‌های برنامه‌نویسان"</string>
- <string name="development_settings_enable" msgid="4285094651288242183">"فعال کردن گزینه‌های برنامه‌نویس"</string>
+ <string name="development_settings_title" msgid="140296922921597393">"گزینه‌های توسعه‌دهندگان"</string>
+ <string name="development_settings_enable" msgid="4285094651288242183">"فعال کردن گزینه‌های توسعه‌دهندگان"</string>
<string name="development_settings_summary" msgid="8718917813868735095">"تنظیم گزینه‌های مربوط به طراحی برنامه"</string>
- <string name="development_settings_not_available" msgid="355070198089140951">"گزینه‌های برنامه‌نویس برای این کاربر موجود نیست"</string>
+ <string name="development_settings_not_available" msgid="355070198089140951">"گزینه‌های توسعه‌دهندگان برای این کاربر موجود نیست"</string>
<string name="vpn_settings_not_available" msgid="2894137119965668920">"‏تنظیمات VPN برای این کاربر در دسترس نیست"</string>
<string name="tethering_settings_not_available" msgid="266821736434699780">"تنظیمات اشتراک‌گذاری اینترنت برای این کاربر در دسترس نیست"</string>
<string name="apn_settings_not_available" msgid="1147111671403342300">"تنظیمات نام تقطه دسترسی برای این کاربر در دسترس نیست"</string>
@@ -287,8 +287,8 @@
<string name="oem_unlock_enable_summary" msgid="5857388174390953829">"اجازه دهید قفل بوت‌لودر باز شود"</string>
<string name="confirm_enable_oem_unlock_title" msgid="8249318129774367535">"‏بازکردن سازنده تجهیزات اصلی مجاز (OEM) است؟"</string>
<string name="confirm_enable_oem_unlock_text" msgid="854131050791011970">"اخطار: قابلیت‌های حفاظتی دستگاه وقتی این تنظیم روشن است در این دستگاه کار نمی‌کند."</string>
- <string name="mock_location_app" msgid="6269380172542248304">"انتخاب برنامه موقعیت مکانی ساختگی"</string>
- <string name="mock_location_app_not_set" msgid="6972032787262831155">"هیچ برنامه موقعیت مکانی ساختگی تنظیم نشده است"</string>
+ <string name="mock_location_app" msgid="6269380172542248304">"انتخاب برنامه مکان ساختگی"</string>
+ <string name="mock_location_app_not_set" msgid="6972032787262831155">"برنامه مکان ساختگی تنظیم نشده است"</string>
<string name="mock_location_app_set" msgid="4706722469342913843">"برنامه موقعیت مکانی ساختگی: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="debug_networking_category" msgid="6829757985772659599">"شبکه"</string>
<string name="wifi_display_certification" msgid="1805579519992520381">"گواهینامه نمایش بی‌سیم"</string>
@@ -376,7 +376,7 @@
<string name="strict_mode_summary" msgid="1838248687233554654">"چشمک زدن صفحه هنگام انجام عملیات طولانی توسط برنامه‌ها در رشته اصلی"</string>
<string name="pointer_location" msgid="7516929526199520173">"محل اشاره‌گر"</string>
<string name="pointer_location_summary" msgid="957120116989798464">"هم‌پوشانی صفحه‌نمایش با نمایش داده لمسی فعلی"</string>
- <string name="show_touches" msgid="8437666942161289025">"نمایش ضربه‌ها"</string>
+ <string name="show_touches" msgid="8437666942161289025">"نمایش تک‌ضرب‌ها"</string>
<string name="show_touches_summary" msgid="3692861665994502193">"نمایش بازخورد تصویری برای ضربه‌ها"</string>
<string name="show_key_presses" msgid="6360141722735900214">"نمایش فشار کلیدها"</string>
<string name="show_key_presses_summary" msgid="725387457373015024">"نمایش بازخورد بصری برای فشار دادن کلیدهای فیزیکی"</string>
diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml
index b92616bc8f1f..6877d75de324 100644
--- a/packages/SettingsLib/res/values-fi/strings.xml
+++ b/packages/SettingsLib/res/values-fi/strings.xml
@@ -582,8 +582,7 @@
<string name="time_unit_just_now" msgid="3006134267292728099">"Äsken"</string>
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Tämä puhelin"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Tämä tabletti"</string>
- <!-- no translation found for media_transfer_this_device_name_desktop (7912386128141470452) -->
- <skip />
+ <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Tämä tietokone (sisäinen)"</string>
<!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
<skip />
<string name="media_transfer_internal_mic" msgid="797333824290228595">"Mikrofoni (sisäinen)"</string>
@@ -687,12 +686,9 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Ei käytössä"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Käytössä"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Laitteesi on käynnistettävä uudelleen, jotta muutos tulee voimaan. Käynnistä uudelleen nyt tai peru."</string>
- <!-- no translation found for media_transfer_wired_headphone_name (8698668536022665254) -->
- <skip />
- <!-- no translation found for media_transfer_headphone_name (1131962659136578852) -->
- <skip />
- <!-- no translation found for media_transfer_usb_speaker_name (4736537022543593896) -->
- <skip />
+ <string name="media_transfer_wired_headphone_name" msgid="8698668536022665254">"Langalliset kuulokkeet"</string>
+ <string name="media_transfer_headphone_name" msgid="1131962659136578852">"Kuulokkeet"</string>
+ <string name="media_transfer_usb_speaker_name" msgid="4736537022543593896">"USB-kaiutin"</string>
<string name="media_transfer_wired_device_mic_name" msgid="7417067197803840965">"Mikrofoniliitäntä"</string>
<string name="media_transfer_usb_device_mic_name" msgid="9189914846215516322">"USB-mikrofoni"</string>
<string name="wifi_hotspot_switch_on_text" msgid="9212273118217786155">"Päällä"</string>
diff --git a/packages/SettingsLib/res/values-fr-rCA/strings.xml b/packages/SettingsLib/res/values-fr-rCA/strings.xml
index bf9adbae0622..b3946ce23688 100644
--- a/packages/SettingsLib/res/values-fr-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-fr-rCA/strings.xml
@@ -582,8 +582,7 @@
<string name="time_unit_just_now" msgid="3006134267292728099">"À l\'instant"</string>
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Ce téléphone"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Cette tablette"</string>
- <!-- no translation found for media_transfer_this_device_name_desktop (7912386128141470452) -->
- <skip />
+ <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Cet ordinateur (interne)"</string>
<!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
<skip />
<string name="media_transfer_internal_mic" msgid="797333824290228595">"Microphone (interne)"</string>
@@ -687,12 +686,9 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Désactivé"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Activé"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Votre appareil doit être redémarré pour que ce changement prenne effet. Redémarrez-le maintenant ou annulez la modification."</string>
- <!-- no translation found for media_transfer_wired_headphone_name (8698668536022665254) -->
- <skip />
- <!-- no translation found for media_transfer_headphone_name (1131962659136578852) -->
- <skip />
- <!-- no translation found for media_transfer_usb_speaker_name (4736537022543593896) -->
- <skip />
+ <string name="media_transfer_wired_headphone_name" msgid="8698668536022665254">"Écouteurs filaires"</string>
+ <string name="media_transfer_headphone_name" msgid="1131962659136578852">"Écouteurs"</string>
+ <string name="media_transfer_usb_speaker_name" msgid="4736537022543593896">"Haut-parleur à port USB"</string>
<string name="media_transfer_wired_device_mic_name" msgid="7417067197803840965">"Prise du microphone"</string>
<string name="media_transfer_usb_device_mic_name" msgid="9189914846215516322">"Microphone USB"</string>
<string name="wifi_hotspot_switch_on_text" msgid="9212273118217786155">"Activé"</string>
diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml
index 0df9489dc60e..13d57ed6ce2d 100644
--- a/packages/SettingsLib/res/values-fr/strings.xml
+++ b/packages/SettingsLib/res/values-fr/strings.xml
@@ -582,8 +582,7 @@
<string name="time_unit_just_now" msgid="3006134267292728099">"À l\'instant"</string>
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Ce téléphone"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Cette tablette"</string>
- <!-- no translation found for media_transfer_this_device_name_desktop (7912386128141470452) -->
- <skip />
+ <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Cet ordinateur (interne)"</string>
<!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
<skip />
<string name="media_transfer_internal_mic" msgid="797333824290228595">"Micro (interne)"</string>
@@ -687,12 +686,9 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Désactivé"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Activé"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Vous devez redémarrer l\'appareil pour que cette modification soit appliquée. Redémarrez maintenant ou annulez l\'opération."</string>
- <!-- no translation found for media_transfer_wired_headphone_name (8698668536022665254) -->
- <skip />
- <!-- no translation found for media_transfer_headphone_name (1131962659136578852) -->
- <skip />
- <!-- no translation found for media_transfer_usb_speaker_name (4736537022543593896) -->
- <skip />
+ <string name="media_transfer_wired_headphone_name" msgid="8698668536022665254">"Casque filaire"</string>
+ <string name="media_transfer_headphone_name" msgid="1131962659136578852">"Casque audio"</string>
+ <string name="media_transfer_usb_speaker_name" msgid="4736537022543593896">"Haut-parleur USB"</string>
<string name="media_transfer_wired_device_mic_name" msgid="7417067197803840965">"Connecteur micro"</string>
<string name="media_transfer_usb_device_mic_name" msgid="9189914846215516322">"Micro USB"</string>
<string name="wifi_hotspot_switch_on_text" msgid="9212273118217786155">"Allumé"</string>
diff --git a/packages/SettingsLib/res/values-gl/strings.xml b/packages/SettingsLib/res/values-gl/strings.xml
index 98a1401f3568..d95e676961b0 100644
--- a/packages/SettingsLib/res/values-gl/strings.xml
+++ b/packages/SettingsLib/res/values-gl/strings.xml
@@ -582,8 +582,7 @@
<string name="time_unit_just_now" msgid="3006134267292728099">"Agora mesmo"</string>
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Este teléfono"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Esta tableta"</string>
- <!-- no translation found for media_transfer_this_device_name_desktop (7912386128141470452) -->
- <skip />
+ <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Este ordenador (interno)"</string>
<!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
<skip />
<string name="media_transfer_internal_mic" msgid="797333824290228595">"Micrófono (interno)"</string>
@@ -687,12 +686,9 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Desactivado"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Activado"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"É necesario reiniciar o teu dispositivo para aplicar este cambio. Reiníciao agora ou cancela o cambio."</string>
- <!-- no translation found for media_transfer_wired_headphone_name (8698668536022665254) -->
- <skip />
- <!-- no translation found for media_transfer_headphone_name (1131962659136578852) -->
- <skip />
- <!-- no translation found for media_transfer_usb_speaker_name (4736537022543593896) -->
- <skip />
+ <string name="media_transfer_wired_headphone_name" msgid="8698668536022665254">"Auriculares con cable"</string>
+ <string name="media_transfer_headphone_name" msgid="1131962659136578852">"Auriculares"</string>
+ <string name="media_transfer_usb_speaker_name" msgid="4736537022543593896">"Altofalante USB"</string>
<string name="media_transfer_wired_device_mic_name" msgid="7417067197803840965">"Conector do micrófono"</string>
<string name="media_transfer_usb_device_mic_name" msgid="9189914846215516322">"Micrófono USB"</string>
<string name="wifi_hotspot_switch_on_text" msgid="9212273118217786155">"Activada"</string>
diff --git a/packages/SettingsLib/res/values-hu/strings.xml b/packages/SettingsLib/res/values-hu/strings.xml
index 62f4777c2337..6cb447ee192d 100644
--- a/packages/SettingsLib/res/values-hu/strings.xml
+++ b/packages/SettingsLib/res/values-hu/strings.xml
@@ -582,8 +582,7 @@
<string name="time_unit_just_now" msgid="3006134267292728099">"Az imént"</string>
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Ez a telefon"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Ez a táblagép"</string>
- <!-- no translation found for media_transfer_this_device_name_desktop (7912386128141470452) -->
- <skip />
+ <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Ez a számítógép (belső)"</string>
<!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
<skip />
<string name="media_transfer_internal_mic" msgid="797333824290228595">"Mikrofon (belső)"</string>
@@ -687,12 +686,9 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Letiltva"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Engedélyezve"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Az eszközt újra kell indítani, hogy a módosítás megtörténjen. Indítsa újra most, vagy vesse el a módosítást."</string>
- <!-- no translation found for media_transfer_wired_headphone_name (8698668536022665254) -->
- <skip />
- <!-- no translation found for media_transfer_headphone_name (1131962659136578852) -->
- <skip />
- <!-- no translation found for media_transfer_usb_speaker_name (4736537022543593896) -->
- <skip />
+ <string name="media_transfer_wired_headphone_name" msgid="8698668536022665254">"Vezetékes fejhallgató"</string>
+ <string name="media_transfer_headphone_name" msgid="1131962659136578852">"Fejhallgató"</string>
+ <string name="media_transfer_usb_speaker_name" msgid="4736537022543593896">"USB-hangszóró"</string>
<string name="media_transfer_wired_device_mic_name" msgid="7417067197803840965">"Mikrofon jack csatlakozója"</string>
<string name="media_transfer_usb_device_mic_name" msgid="9189914846215516322">"USB-mikrofon"</string>
<string name="wifi_hotspot_switch_on_text" msgid="9212273118217786155">"Be"</string>
diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml
index ba4e2043827f..0e25b7904548 100644
--- a/packages/SettingsLib/res/values-hy/strings.xml
+++ b/packages/SettingsLib/res/values-hy/strings.xml
@@ -582,8 +582,7 @@
<string name="time_unit_just_now" msgid="3006134267292728099">"Հենց նոր"</string>
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Այս հեռախոսը"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Այս պլանշետը"</string>
- <!-- no translation found for media_transfer_this_device_name_desktop (7912386128141470452) -->
- <skip />
+ <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Այս համակարգիչը (ներքին)"</string>
<!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
<skip />
<string name="media_transfer_internal_mic" msgid="797333824290228595">"Խոսափող (ներքին)"</string>
@@ -687,12 +686,9 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Անջատված է"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Միացված է"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Սարքն անհրաժեշտ է վերագործարկել, որպեսզի փոփոխությունը կիրառվի։ Վերագործարկեք հիմա կամ չեղարկեք փոփոխությունը։"</string>
- <!-- no translation found for media_transfer_wired_headphone_name (8698668536022665254) -->
- <skip />
- <!-- no translation found for media_transfer_headphone_name (1131962659136578852) -->
- <skip />
- <!-- no translation found for media_transfer_usb_speaker_name (4736537022543593896) -->
- <skip />
+ <string name="media_transfer_wired_headphone_name" msgid="8698668536022665254">"Լարով ականջակալ"</string>
+ <string name="media_transfer_headphone_name" msgid="1131962659136578852">"Ականջակալ"</string>
+ <string name="media_transfer_usb_speaker_name" msgid="4736537022543593896">"USB բարձրախոս"</string>
<string name="media_transfer_wired_device_mic_name" msgid="7417067197803840965">"Խոսափողի հարակցիչ"</string>
<string name="media_transfer_usb_device_mic_name" msgid="9189914846215516322">"USB խոսափող"</string>
<string name="wifi_hotspot_switch_on_text" msgid="9212273118217786155">"Միացնել"</string>
diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml
index 89d82f002fa9..ceb948daa72c 100644
--- a/packages/SettingsLib/res/values-in/strings.xml
+++ b/packages/SettingsLib/res/values-in/strings.xml
@@ -582,8 +582,7 @@
<string name="time_unit_just_now" msgid="3006134267292728099">"Baru saja"</string>
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Ponsel ini"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Tablet ini"</string>
- <!-- no translation found for media_transfer_this_device_name_desktop (7912386128141470452) -->
- <skip />
+ <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Komputer ini (internal)"</string>
<!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
<skip />
<string name="media_transfer_internal_mic" msgid="797333824290228595">"Mikrofon (internal)"</string>
@@ -687,12 +686,9 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Nonaktif"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Aktif"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Perangkat Anda harus di-reboot agar perubahan ini diterapkan. Reboot sekarang atau batalkan."</string>
- <!-- no translation found for media_transfer_wired_headphone_name (8698668536022665254) -->
- <skip />
- <!-- no translation found for media_transfer_headphone_name (1131962659136578852) -->
- <skip />
- <!-- no translation found for media_transfer_usb_speaker_name (4736537022543593896) -->
- <skip />
+ <string name="media_transfer_wired_headphone_name" msgid="8698668536022665254">"Headphone berkabel"</string>
+ <string name="media_transfer_headphone_name" msgid="1131962659136578852">"Headphone"</string>
+ <string name="media_transfer_usb_speaker_name" msgid="4736537022543593896">"Speaker USB"</string>
<string name="media_transfer_wired_device_mic_name" msgid="7417067197803840965">"Colokan mikrofon"</string>
<string name="media_transfer_usb_device_mic_name" msgid="9189914846215516322">"Mikrofon USB"</string>
<string name="wifi_hotspot_switch_on_text" msgid="9212273118217786155">"Aktif"</string>
diff --git a/packages/SettingsLib/res/values-is/strings.xml b/packages/SettingsLib/res/values-is/strings.xml
index b5d1831ad27a..fd31a6f99c6e 100644
--- a/packages/SettingsLib/res/values-is/strings.xml
+++ b/packages/SettingsLib/res/values-is/strings.xml
@@ -582,8 +582,7 @@
<string name="time_unit_just_now" msgid="3006134267292728099">"Rétt í þessu"</string>
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Þessi sími"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Þessi spjaldtölva"</string>
- <!-- no translation found for media_transfer_this_device_name_desktop (7912386128141470452) -->
- <skip />
+ <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Þessi tölva (innbyggður)"</string>
<!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
<skip />
<string name="media_transfer_internal_mic" msgid="797333824290228595">"Hljóðnemi (innbyggður)"</string>
@@ -687,12 +686,9 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Slökkt"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Virkt"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Endurræsa þarf tækið til að þessi breyting taki gildi. Endurræstu núna eða hættu við."</string>
- <!-- no translation found for media_transfer_wired_headphone_name (8698668536022665254) -->
- <skip />
- <!-- no translation found for media_transfer_headphone_name (1131962659136578852) -->
- <skip />
- <!-- no translation found for media_transfer_usb_speaker_name (4736537022543593896) -->
- <skip />
+ <string name="media_transfer_wired_headphone_name" msgid="8698668536022665254">"Heyrnartól með snúru"</string>
+ <string name="media_transfer_headphone_name" msgid="1131962659136578852">"Heyrnartól"</string>
+ <string name="media_transfer_usb_speaker_name" msgid="4736537022543593896">"USB-hátalari"</string>
<string name="media_transfer_wired_device_mic_name" msgid="7417067197803840965">"Hljóðnematengi"</string>
<string name="media_transfer_usb_device_mic_name" msgid="9189914846215516322">"USB-hljóðnemi"</string>
<string name="wifi_hotspot_switch_on_text" msgid="9212273118217786155">"Kveikt"</string>
diff --git a/packages/SettingsLib/res/values-it/strings.xml b/packages/SettingsLib/res/values-it/strings.xml
index 1436c1733ebd..c2931fc2d257 100644
--- a/packages/SettingsLib/res/values-it/strings.xml
+++ b/packages/SettingsLib/res/values-it/strings.xml
@@ -582,8 +582,7 @@
<string name="time_unit_just_now" msgid="3006134267292728099">"Adesso"</string>
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Questo smartphone"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Questo tablet"</string>
- <!-- no translation found for media_transfer_this_device_name_desktop (7912386128141470452) -->
- <skip />
+ <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Questo computer (interno)"</string>
<!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
<skip />
<string name="media_transfer_internal_mic" msgid="797333824290228595">"Microfono (interno)"</string>
@@ -687,12 +686,9 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Non attivo"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Attivo"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Per applicare questa modifica, devi riavviare il dispositivo. Riavvia ora o annulla."</string>
- <!-- no translation found for media_transfer_wired_headphone_name (8698668536022665254) -->
- <skip />
- <!-- no translation found for media_transfer_headphone_name (1131962659136578852) -->
- <skip />
- <!-- no translation found for media_transfer_usb_speaker_name (4736537022543593896) -->
- <skip />
+ <string name="media_transfer_wired_headphone_name" msgid="8698668536022665254">"Cuffie con cavo"</string>
+ <string name="media_transfer_headphone_name" msgid="1131962659136578852">"Cuffie"</string>
+ <string name="media_transfer_usb_speaker_name" msgid="4736537022543593896">"Altoparlante USB"</string>
<string name="media_transfer_wired_device_mic_name" msgid="7417067197803840965">"Jack per microfono"</string>
<string name="media_transfer_usb_device_mic_name" msgid="9189914846215516322">"Microfono USB"</string>
<string name="wifi_hotspot_switch_on_text" msgid="9212273118217786155">"On"</string>
diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml
index 986fcb978ae4..bcf7e92ae375 100644
--- a/packages/SettingsLib/res/values-iw/strings.xml
+++ b/packages/SettingsLib/res/values-iw/strings.xml
@@ -582,8 +582,7 @@
<string name="time_unit_just_now" msgid="3006134267292728099">"הרגע"</string>
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"הטלפון הזה"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"הטאבלט הזה"</string>
- <!-- no translation found for media_transfer_this_device_name_desktop (7912386128141470452) -->
- <skip />
+ <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"המחשב הזה (פנימי)"</string>
<!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
<skip />
<string name="media_transfer_internal_mic" msgid="797333824290228595">"מיקרופון (פנימי)"</string>
@@ -687,12 +686,9 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"מושבת"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"מופעל"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"צריך להפעיל מחדש את המכשיר כדי להחיל את השינוי. יש להפעיל מחדש עכשיו או לבטל."</string>
- <!-- no translation found for media_transfer_wired_headphone_name (8698668536022665254) -->
- <skip />
- <!-- no translation found for media_transfer_headphone_name (1131962659136578852) -->
- <skip />
- <!-- no translation found for media_transfer_usb_speaker_name (4736537022543593896) -->
- <skip />
+ <string name="media_transfer_wired_headphone_name" msgid="8698668536022665254">"אוזניות חוטיות"</string>
+ <string name="media_transfer_headphone_name" msgid="1131962659136578852">"אוזניות"</string>
+ <string name="media_transfer_usb_speaker_name" msgid="4736537022543593896">"‏רמקול ב-USB"</string>
<string name="media_transfer_wired_device_mic_name" msgid="7417067197803840965">"שקע למיקרופון"</string>
<string name="media_transfer_usb_device_mic_name" msgid="9189914846215516322">"‏מיקרופון USB"</string>
<string name="wifi_hotspot_switch_on_text" msgid="9212273118217786155">"פועלת"</string>
diff --git a/packages/SettingsLib/res/values-kk/strings.xml b/packages/SettingsLib/res/values-kk/strings.xml
index 6789f409fc2e..4f64bb9cf5d9 100644
--- a/packages/SettingsLib/res/values-kk/strings.xml
+++ b/packages/SettingsLib/res/values-kk/strings.xml
@@ -582,8 +582,7 @@
<string name="time_unit_just_now" msgid="3006134267292728099">"Дәл қазір"</string>
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Осы телефон"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Осы планшет"</string>
- <!-- no translation found for media_transfer_this_device_name_desktop (7912386128141470452) -->
- <skip />
+ <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Осы компьютер (ішкі)"</string>
<!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
<skip />
<string name="media_transfer_internal_mic" msgid="797333824290228595">"Микрофон (ішкі)"</string>
@@ -687,12 +686,9 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Өшірулі"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Қосулы"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Бұл өзгеріс күшіне енуі үшін, құрылғыны қайта жүктеу керек. Қазір қайта жүктеңіз не бас тартыңыз."</string>
- <!-- no translation found for media_transfer_wired_headphone_name (8698668536022665254) -->
- <skip />
- <!-- no translation found for media_transfer_headphone_name (1131962659136578852) -->
- <skip />
- <!-- no translation found for media_transfer_usb_speaker_name (4736537022543593896) -->
- <skip />
+ <string name="media_transfer_wired_headphone_name" msgid="8698668536022665254">"Сымды құлақаспап"</string>
+ <string name="media_transfer_headphone_name" msgid="1131962659136578852">"Құлақаспап"</string>
+ <string name="media_transfer_usb_speaker_name" msgid="4736537022543593896">"USB динамик"</string>
<string name="media_transfer_wired_device_mic_name" msgid="7417067197803840965">"Микрофон ұяшығы"</string>
<string name="media_transfer_usb_device_mic_name" msgid="9189914846215516322">"USB микрофоны"</string>
<string name="wifi_hotspot_switch_on_text" msgid="9212273118217786155">"Қосу"</string>
diff --git a/packages/SettingsLib/res/values-kn/strings.xml b/packages/SettingsLib/res/values-kn/strings.xml
index 29417d600dc3..016f8b9d4003 100644
--- a/packages/SettingsLib/res/values-kn/strings.xml
+++ b/packages/SettingsLib/res/values-kn/strings.xml
@@ -582,8 +582,7 @@
<string name="time_unit_just_now" msgid="3006134267292728099">"ಇದೀಗ"</string>
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"ಈ ಫೋನ್"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"ಈ ಟ್ಯಾಬ್ಲೆಟ್"</string>
- <!-- no translation found for media_transfer_this_device_name_desktop (7912386128141470452) -->
- <skip />
+ <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"ಈ ಕಂಪ್ಯೂಟರ್ (ಆಂತರಿಕ)"</string>
<!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
<skip />
<string name="media_transfer_internal_mic" msgid="797333824290228595">"ಮೈಕ್ರೊಫೋನ್‌ (ಆಂತರಿಕ)"</string>
@@ -687,12 +686,9 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"ಸಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"ಈ ಬದಲಾವಣೆ ಅನ್ವಯವಾಗಲು ನಿಮ್ಮ ಸಾಧನವನ್ನು ರೀಬೂಟ್ ಮಾಡಬೇಕು. ಇದೀಗ ರೀಬೂಟ್ ಮಾಡಿ ಅಥವಾ ರದ್ದುಗೊಳಿಸಿ."</string>
- <!-- no translation found for media_transfer_wired_headphone_name (8698668536022665254) -->
- <skip />
- <!-- no translation found for media_transfer_headphone_name (1131962659136578852) -->
- <skip />
- <!-- no translation found for media_transfer_usb_speaker_name (4736537022543593896) -->
- <skip />
+ <string name="media_transfer_wired_headphone_name" msgid="8698668536022665254">"ವೈಯರ್ ಹೊಂದಿರುವ ಹೆಡ್‌ಫೋನ್"</string>
+ <string name="media_transfer_headphone_name" msgid="1131962659136578852">"ಹೆಡ್‌ಫೋನ್"</string>
+ <string name="media_transfer_usb_speaker_name" msgid="4736537022543593896">"USB ಸ್ಪೀಕರ್"</string>
<string name="media_transfer_wired_device_mic_name" msgid="7417067197803840965">"ಮೈಕ್‌ ಜ್ಯಾಕ್‌"</string>
<string name="media_transfer_usb_device_mic_name" msgid="9189914846215516322">"USB ಮೈಕ್‌"</string>
<string name="wifi_hotspot_switch_on_text" msgid="9212273118217786155">"ಆನ್ ಆಗಿದೆ"</string>
diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml
index 4b0cfb4802d7..0af77dd660c0 100644
--- a/packages/SettingsLib/res/values-ko/strings.xml
+++ b/packages/SettingsLib/res/values-ko/strings.xml
@@ -582,8 +582,7 @@
<string name="time_unit_just_now" msgid="3006134267292728099">"조금 전"</string>
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"이 휴대전화"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"이 태블릿"</string>
- <!-- no translation found for media_transfer_this_device_name_desktop (7912386128141470452) -->
- <skip />
+ <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"이 컴퓨터(내부)"</string>
<!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
<skip />
<string name="media_transfer_internal_mic" msgid="797333824290228595">"마이크(내부)"</string>
@@ -687,12 +686,9 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"사용 중지됨"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"사용 설정됨"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"변경사항을 적용하려면 기기를 재부팅해야 합니다. 지금 재부팅하거나 취소하세요."</string>
- <!-- no translation found for media_transfer_wired_headphone_name (8698668536022665254) -->
- <skip />
- <!-- no translation found for media_transfer_headphone_name (1131962659136578852) -->
- <skip />
- <!-- no translation found for media_transfer_usb_speaker_name (4736537022543593896) -->
- <skip />
+ <string name="media_transfer_wired_headphone_name" msgid="8698668536022665254">"유선 헤드폰"</string>
+ <string name="media_transfer_headphone_name" msgid="1131962659136578852">"헤드폰"</string>
+ <string name="media_transfer_usb_speaker_name" msgid="4736537022543593896">"USB 스피커"</string>
<string name="media_transfer_wired_device_mic_name" msgid="7417067197803840965">"마이크 잭"</string>
<string name="media_transfer_usb_device_mic_name" msgid="9189914846215516322">"USB 마이크"</string>
<string name="wifi_hotspot_switch_on_text" msgid="9212273118217786155">"사용"</string>
diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml
index 17b55c4ef97a..a3ff033ac0f4 100644
--- a/packages/SettingsLib/res/values-ky/strings.xml
+++ b/packages/SettingsLib/res/values-ky/strings.xml
@@ -582,8 +582,7 @@
<string name="time_unit_just_now" msgid="3006134267292728099">"Жаңы эле"</string>
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Ушул телефон"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Ушул планшет"</string>
- <!-- no translation found for media_transfer_this_device_name_desktop (7912386128141470452) -->
- <skip />
+ <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Бул компьютер (ички)"</string>
<!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
<skip />
<string name="media_transfer_internal_mic" msgid="797333824290228595">"Микрофон (ички)"</string>
@@ -687,12 +686,9 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Өчүк"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Күйүк"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Бул өзгөрүү күчүнө кириши үчүн, түзмөктү өчүрүп күйгүзүңүз. Азыр же кийинчерээк өчүрүп күйгүзсөңүз болот."</string>
- <!-- no translation found for media_transfer_wired_headphone_name (8698668536022665254) -->
- <skip />
- <!-- no translation found for media_transfer_headphone_name (1131962659136578852) -->
- <skip />
- <!-- no translation found for media_transfer_usb_speaker_name (4736537022543593896) -->
- <skip />
+ <string name="media_transfer_wired_headphone_name" msgid="8698668536022665254">"Зымдуу гарнитура"</string>
+ <string name="media_transfer_headphone_name" msgid="1131962659136578852">"Гарнитура"</string>
+ <string name="media_transfer_usb_speaker_name" msgid="4736537022543593896">"USB динамиги"</string>
<string name="media_transfer_wired_device_mic_name" msgid="7417067197803840965">"Микрофондун оюкчасы"</string>
<string name="media_transfer_usb_device_mic_name" msgid="9189914846215516322">"USB микрофон"</string>
<string name="wifi_hotspot_switch_on_text" msgid="9212273118217786155">"Күйгүзүү"</string>
diff --git a/packages/SettingsLib/res/values-lo/strings.xml b/packages/SettingsLib/res/values-lo/strings.xml
index e6e8ef89fb74..1d89e3e5e68f 100644
--- a/packages/SettingsLib/res/values-lo/strings.xml
+++ b/packages/SettingsLib/res/values-lo/strings.xml
@@ -582,8 +582,7 @@
<string name="time_unit_just_now" msgid="3006134267292728099">"ຕອນນີ້"</string>
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"ໂທລະສັບນີ້"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"ແທັບເລັດນີ້"</string>
- <!-- no translation found for media_transfer_this_device_name_desktop (7912386128141470452) -->
- <skip />
+ <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"ຄອມພິວເຕີນີ້ (ພາຍໃນ)"</string>
<!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
<skip />
<string name="media_transfer_internal_mic" msgid="797333824290228595">"ໄມໂຄຣໂຟນ (ພາຍໃນ)"</string>
@@ -687,12 +686,9 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"ປິດການນຳໃຊ້ແລ້ວ"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"ເປີດການນຳໃຊ້ແລ້ວ"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"ທ່ານຕ້ອງປິດເປີດອຸປະກອນຄືນໃໝ່ເພື່ອນຳໃຊ້ການປ່ຽນແປງນີ້. ປິດເປີດໃໝ່ດຽວນີ້ ຫຼື ຍົກເລີກ."</string>
- <!-- no translation found for media_transfer_wired_headphone_name (8698668536022665254) -->
- <skip />
- <!-- no translation found for media_transfer_headphone_name (1131962659136578852) -->
- <skip />
- <!-- no translation found for media_transfer_usb_speaker_name (4736537022543593896) -->
- <skip />
+ <string name="media_transfer_wired_headphone_name" msgid="8698668536022665254">"ຫູຟັງແບບມີສາຍ"</string>
+ <string name="media_transfer_headphone_name" msgid="1131962659136578852">"ຫູຟັງ"</string>
+ <string name="media_transfer_usb_speaker_name" msgid="4736537022543593896">"ລຳໂພງ USB"</string>
<string name="media_transfer_wired_device_mic_name" msgid="7417067197803840965">"ຊ່ອງສຽງໄມ"</string>
<string name="media_transfer_usb_device_mic_name" msgid="9189914846215516322">"ໄມ USB"</string>
<string name="wifi_hotspot_switch_on_text" msgid="9212273118217786155">"ເປີດ"</string>
diff --git a/packages/SettingsLib/res/values-lt/strings.xml b/packages/SettingsLib/res/values-lt/strings.xml
index 5945e2a38a7c..265eb9aa0618 100644
--- a/packages/SettingsLib/res/values-lt/strings.xml
+++ b/packages/SettingsLib/res/values-lt/strings.xml
@@ -582,8 +582,7 @@
<string name="time_unit_just_now" msgid="3006134267292728099">"Ką tik"</string>
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Šis telefonas"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Šis planšetinis kompiuteris"</string>
- <!-- no translation found for media_transfer_this_device_name_desktop (7912386128141470452) -->
- <skip />
+ <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Šis kompiuteris (vidinis)"</string>
<!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
<skip />
<string name="media_transfer_internal_mic" msgid="797333824290228595">"Mikrofonas (vidinis)"</string>
@@ -687,12 +686,9 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Išjungta"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Įgalinta"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Kad pakeitimas būtų pritaikytas, įrenginį reikia paleisti iš naujo. Dabar paleiskite iš naujo arba atšaukite."</string>
- <!-- no translation found for media_transfer_wired_headphone_name (8698668536022665254) -->
- <skip />
- <!-- no translation found for media_transfer_headphone_name (1131962659136578852) -->
- <skip />
- <!-- no translation found for media_transfer_usb_speaker_name (4736537022543593896) -->
- <skip />
+ <string name="media_transfer_wired_headphone_name" msgid="8698668536022665254">"Laidinės ausinės"</string>
+ <string name="media_transfer_headphone_name" msgid="1131962659136578852">"Ausinės"</string>
+ <string name="media_transfer_usb_speaker_name" msgid="4736537022543593896">"USB garsiakalbis"</string>
<string name="media_transfer_wired_device_mic_name" msgid="7417067197803840965">"Mikrofono jungtis"</string>
<string name="media_transfer_usb_device_mic_name" msgid="9189914846215516322">"USB mikrofonas"</string>
<string name="wifi_hotspot_switch_on_text" msgid="9212273118217786155">"Įjungta"</string>
diff --git a/packages/SettingsLib/res/values-lv/strings.xml b/packages/SettingsLib/res/values-lv/strings.xml
index 34f6c4c2c120..c24dafbaf278 100644
--- a/packages/SettingsLib/res/values-lv/strings.xml
+++ b/packages/SettingsLib/res/values-lv/strings.xml
@@ -582,8 +582,7 @@
<string name="time_unit_just_now" msgid="3006134267292728099">"Tikko"</string>
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Šis tālrunis"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Šis planšetdators"</string>
- <!-- no translation found for media_transfer_this_device_name_desktop (7912386128141470452) -->
- <skip />
+ <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Šī datora iekšējais skaļrunis"</string>
<!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
<skip />
<string name="media_transfer_internal_mic" msgid="797333824290228595">"Mikrofons (iebūvētais)"</string>
@@ -687,12 +686,9 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Atspējots"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Iespējots"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Lai šīs izmaiņas tiktu piemērotas, nepieciešama ierīces atkārtota palaišana. Atkārtoti palaidiet to tūlīt vai atceliet izmaiņas."</string>
- <!-- no translation found for media_transfer_wired_headphone_name (8698668536022665254) -->
- <skip />
- <!-- no translation found for media_transfer_headphone_name (1131962659136578852) -->
- <skip />
- <!-- no translation found for media_transfer_usb_speaker_name (4736537022543593896) -->
- <skip />
+ <string name="media_transfer_wired_headphone_name" msgid="8698668536022665254">"Vadu austiņas"</string>
+ <string name="media_transfer_headphone_name" msgid="1131962659136578852">"Austiņas"</string>
+ <string name="media_transfer_usb_speaker_name" msgid="4736537022543593896">"USB skaļrunis"</string>
<string name="media_transfer_wired_device_mic_name" msgid="7417067197803840965">"Mikrofona ligzda"</string>
<string name="media_transfer_usb_device_mic_name" msgid="9189914846215516322">"USB mikrofons"</string>
<string name="wifi_hotspot_switch_on_text" msgid="9212273118217786155">"Ieslēgts"</string>
diff --git a/packages/SettingsLib/res/values-mn/strings.xml b/packages/SettingsLib/res/values-mn/strings.xml
index bc953d374a91..9fdbdc693dc8 100644
--- a/packages/SettingsLib/res/values-mn/strings.xml
+++ b/packages/SettingsLib/res/values-mn/strings.xml
@@ -582,8 +582,7 @@
<string name="time_unit_just_now" msgid="3006134267292728099">"Дөнгөж сая"</string>
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Энэ утас"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Энэ таблет"</string>
- <!-- no translation found for media_transfer_this_device_name_desktop (7912386128141470452) -->
- <skip />
+ <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Энэ компьютер (дотоод)"</string>
<!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
<skip />
<string name="media_transfer_internal_mic" msgid="797333824290228595">"Микрофон (дотоод)"</string>
@@ -687,12 +686,9 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Идэвхгүй болгосон"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Идэвхжүүлсэн"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Энэ өөрчлөлтийг хэрэгжүүлэхийн тулд таны төхөөрөмжийг дахин асаах ёстой. Одоо дахин асаах эсвэл цуцлана уу."</string>
- <!-- no translation found for media_transfer_wired_headphone_name (8698668536022665254) -->
- <skip />
- <!-- no translation found for media_transfer_headphone_name (1131962659136578852) -->
- <skip />
- <!-- no translation found for media_transfer_usb_speaker_name (4736537022543593896) -->
- <skip />
+ <string name="media_transfer_wired_headphone_name" msgid="8698668536022665254">"Утастай чихэвч"</string>
+ <string name="media_transfer_headphone_name" msgid="1131962659136578852">"Чихэвч"</string>
+ <string name="media_transfer_usb_speaker_name" msgid="4736537022543593896">"USB чанга яригч"</string>
<string name="media_transfer_wired_device_mic_name" msgid="7417067197803840965">"Микрофоны чихэвчний оролт"</string>
<string name="media_transfer_usb_device_mic_name" msgid="9189914846215516322">"USB микрофон"</string>
<string name="wifi_hotspot_switch_on_text" msgid="9212273118217786155">"Асаах"</string>
diff --git a/packages/SettingsLib/res/values-my/strings.xml b/packages/SettingsLib/res/values-my/strings.xml
index 063fc9c1f268..8fbc8bdc867e 100644
--- a/packages/SettingsLib/res/values-my/strings.xml
+++ b/packages/SettingsLib/res/values-my/strings.xml
@@ -582,8 +582,7 @@
<string name="time_unit_just_now" msgid="3006134267292728099">"ယခုလေးတင်"</string>
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"ဤဖုန်း"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"ဤတက်ဘလက်"</string>
- <!-- no translation found for media_transfer_this_device_name_desktop (7912386128141470452) -->
- <skip />
+ <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"ဤကွန်ပျူတာ (စက်တွင်း)"</string>
<!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
<skip />
<string name="media_transfer_internal_mic" msgid="797333824290228595">"မိုက်ခရိုဖုန်း (စက်တွင်း)"</string>
@@ -687,12 +686,9 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"ပိတ်ထားသည်"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"ဖွင့်ထားသည်"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"ဤအပြောင်းအလဲ ထည့်သွင်းရန် သင့်စက်ကို ပြန်လည်စတင်ရမည်။ ယခု ပြန်လည်စတင်ပါ သို့မဟုတ် ပယ်ဖျက်ပါ။"</string>
- <!-- no translation found for media_transfer_wired_headphone_name (8698668536022665254) -->
- <skip />
- <!-- no translation found for media_transfer_headphone_name (1131962659136578852) -->
- <skip />
- <!-- no translation found for media_transfer_usb_speaker_name (4736537022543593896) -->
- <skip />
+ <string name="media_transfer_wired_headphone_name" msgid="8698668536022665254">"ကြိုးတပ်နားကြပ်"</string>
+ <string name="media_transfer_headphone_name" msgid="1131962659136578852">"နားကြပ်"</string>
+ <string name="media_transfer_usb_speaker_name" msgid="4736537022543593896">"USB စပီကာ"</string>
<string name="media_transfer_wired_device_mic_name" msgid="7417067197803840965">"မိုက်ဂျက်ပင်"</string>
<string name="media_transfer_usb_device_mic_name" msgid="9189914846215516322">"USB မိုက်"</string>
<string name="wifi_hotspot_switch_on_text" msgid="9212273118217786155">"ဖွင့်"</string>
diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml
index 9036c8c80158..569b900bd524 100644
--- a/packages/SettingsLib/res/values-nb/strings.xml
+++ b/packages/SettingsLib/res/values-nb/strings.xml
@@ -582,8 +582,7 @@
<string name="time_unit_just_now" msgid="3006134267292728099">"Nå nettopp"</string>
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Denne telefonen"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Dette nettbrettet"</string>
- <!-- no translation found for media_transfer_this_device_name_desktop (7912386128141470452) -->
- <skip />
+ <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Denne datamaskinen (intern)"</string>
<!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
<skip />
<string name="media_transfer_internal_mic" msgid="797333824290228595">"Mikrofon (intern)"</string>
@@ -687,12 +686,9 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Slått av"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Slått på"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Enheten din må startes på nytt for at denne endringen skal tre i kraft. Start på nytt nå eller avbryt."</string>
- <!-- no translation found for media_transfer_wired_headphone_name (8698668536022665254) -->
- <skip />
- <!-- no translation found for media_transfer_headphone_name (1131962659136578852) -->
- <skip />
- <!-- no translation found for media_transfer_usb_speaker_name (4736537022543593896) -->
- <skip />
+ <string name="media_transfer_wired_headphone_name" msgid="8698668536022665254">"Hodetelefoner med kabel"</string>
+ <string name="media_transfer_headphone_name" msgid="1131962659136578852">"Hodetelefoner"</string>
+ <string name="media_transfer_usb_speaker_name" msgid="4736537022543593896">"USB-høyttaler"</string>
<string name="media_transfer_wired_device_mic_name" msgid="7417067197803840965">"Mikrofonkontakt"</string>
<string name="media_transfer_usb_device_mic_name" msgid="9189914846215516322">"USB-mikrofon"</string>
<string name="wifi_hotspot_switch_on_text" msgid="9212273118217786155">"På"</string>
diff --git a/packages/SettingsLib/res/values-or/strings.xml b/packages/SettingsLib/res/values-or/strings.xml
index 1b2e4217fa5f..c2439f9c304a 100644
--- a/packages/SettingsLib/res/values-or/strings.xml
+++ b/packages/SettingsLib/res/values-or/strings.xml
@@ -582,8 +582,7 @@
<string name="time_unit_just_now" msgid="3006134267292728099">"ଏହିକ୍ଷଣି"</string>
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"ଏହି ଫୋନ"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"ଏହି ଟାବଲେଟ"</string>
- <!-- no translation found for media_transfer_this_device_name_desktop (7912386128141470452) -->
- <skip />
+ <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"ଏହି କମ୍ପ୍ୟୁଟର (ଇଣ୍ଟର୍ନଲ)"</string>
<!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
<skip />
<string name="media_transfer_internal_mic" msgid="797333824290228595">"ମାଇକ୍ରୋଫୋନ (ଇଣ୍ଟର୍ନଲ)"</string>
@@ -687,12 +686,9 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"ଅକ୍ଷମ କରାଯାଇଛି"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"ସକ୍ଷମ କରାଯାଇଛି"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"ଏହି ପରିବର୍ତ୍ତନ ଲାଗୁ କରିବା ପାଇଁ ଆପଣଙ୍କ ଡିଭାଇସକୁ ନିଶ୍ଚିତ ରୂପେ ରିବୁଟ୍ କରାଯିବା ଆବଶ୍ୟକ। ବର୍ତ୍ତମାନ ରିବୁଟ୍ କରନ୍ତୁ କିମ୍ବା ବାତିଲ କରନ୍ତୁ।"</string>
- <!-- no translation found for media_transfer_wired_headphone_name (8698668536022665254) -->
- <skip />
- <!-- no translation found for media_transfer_headphone_name (1131962659136578852) -->
- <skip />
- <!-- no translation found for media_transfer_usb_speaker_name (4736537022543593896) -->
- <skip />
+ <string name="media_transfer_wired_headphone_name" msgid="8698668536022665254">"ତାରଯୁକ୍ତ ହେଡଫୋନ"</string>
+ <string name="media_transfer_headphone_name" msgid="1131962659136578852">"ହେଡଫୋନ"</string>
+ <string name="media_transfer_usb_speaker_name" msgid="4736537022543593896">"USB ସ୍ପିକର"</string>
<string name="media_transfer_wired_device_mic_name" msgid="7417067197803840965">"ମାଇକ ଜେକ"</string>
<string name="media_transfer_usb_device_mic_name" msgid="9189914846215516322">"USB ମାଇକ"</string>
<string name="wifi_hotspot_switch_on_text" msgid="9212273118217786155">"ଚାଲୁ ଅଛି"</string>
diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml
index c668e1256009..4a4f9248daba 100644
--- a/packages/SettingsLib/res/values-pa/strings.xml
+++ b/packages/SettingsLib/res/values-pa/strings.xml
@@ -582,8 +582,7 @@
<string name="time_unit_just_now" msgid="3006134267292728099">"ਹੁਣੇ ਹੀ"</string>
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"ਇਹ ਫ਼ੋਨ"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"ਇਹ ਟੈਬਲੈੱਟ"</string>
- <!-- no translation found for media_transfer_this_device_name_desktop (7912386128141470452) -->
- <skip />
+ <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"ਇਸ ਕੰਪਿਊਟਰ \'ਤੇ (ਅੰਦਰੂਨੀ)"</string>
<!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
<skip />
<string name="media_transfer_internal_mic" msgid="797333824290228595">"ਮਾਈਕ੍ਰੋਫ਼ੋਨ (ਅੰਦਰੂਨੀ)"</string>
@@ -687,12 +686,9 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"ਬੰਦ ਕੀਤਾ ਗਿਆ"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"ਚਾਲੂ ਕੀਤਾ ਗਿਆ"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"ਇਸ ਤਬਦੀਲੀ ਨੂੰ ਲਾਗੂ ਕਰਨ ਲਈ ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਨੂੰ ਰੀਬੂਟ ਕਰਨਾ ਲਾਜ਼ਮੀ ਹੈ। ਹੁਣੇ ਰੀਬੂਟ ਕਰੋ ਜਾਂ ਰੱਦ ਕਰੋ।"</string>
- <!-- no translation found for media_transfer_wired_headphone_name (8698668536022665254) -->
- <skip />
- <!-- no translation found for media_transfer_headphone_name (1131962659136578852) -->
- <skip />
- <!-- no translation found for media_transfer_usb_speaker_name (4736537022543593896) -->
- <skip />
+ <string name="media_transfer_wired_headphone_name" msgid="8698668536022665254">"ਤਾਰ ਵਾਲੇ ਹੈੱਡਫ਼ੋਨ"</string>
+ <string name="media_transfer_headphone_name" msgid="1131962659136578852">"ਹੈੱਡਫ਼ੋਨ"</string>
+ <string name="media_transfer_usb_speaker_name" msgid="4736537022543593896">"USB ਸਪੀਕਰ"</string>
<string name="media_transfer_wired_device_mic_name" msgid="7417067197803840965">"ਮਾਈਕ ਜੈਕ"</string>
<string name="media_transfer_usb_device_mic_name" msgid="9189914846215516322">"USB ਮਾਈਕ"</string>
<string name="wifi_hotspot_switch_on_text" msgid="9212273118217786155">"ਚਾਲੂ"</string>
diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml
index 92ab714d75b0..f489d916fbf0 100644
--- a/packages/SettingsLib/res/values-pt-rPT/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml
@@ -582,8 +582,7 @@
<string name="time_unit_just_now" msgid="3006134267292728099">"Agora mesmo"</string>
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Este telemóvel"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Este tablet"</string>
- <!-- no translation found for media_transfer_this_device_name_desktop (7912386128141470452) -->
- <skip />
+ <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Este computador (interno)"</string>
<!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
<skip />
<string name="media_transfer_internal_mic" msgid="797333824290228595">"Microfone (interno)"</string>
@@ -687,12 +686,9 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Desativada"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Ativada"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"É necessário reiniciar o dispositivo para aplicar esta alteração. Reinicie agora ou cancele."</string>
- <!-- no translation found for media_transfer_wired_headphone_name (8698668536022665254) -->
- <skip />
- <!-- no translation found for media_transfer_headphone_name (1131962659136578852) -->
- <skip />
- <!-- no translation found for media_transfer_usb_speaker_name (4736537022543593896) -->
- <skip />
+ <string name="media_transfer_wired_headphone_name" msgid="8698668536022665254">"Auscultadores com fios"</string>
+ <string name="media_transfer_headphone_name" msgid="1131962659136578852">"Auscultadores"</string>
+ <string name="media_transfer_usb_speaker_name" msgid="4736537022543593896">"Altifalante USB"</string>
<string name="media_transfer_wired_device_mic_name" msgid="7417067197803840965">"Entrada para microfone"</string>
<string name="media_transfer_usb_device_mic_name" msgid="9189914846215516322">"Microfone USB"</string>
<string name="wifi_hotspot_switch_on_text" msgid="9212273118217786155">"Ligado"</string>
diff --git a/packages/SettingsLib/res/values-ro/strings.xml b/packages/SettingsLib/res/values-ro/strings.xml
index e71063464921..f79c2e1e0634 100644
--- a/packages/SettingsLib/res/values-ro/strings.xml
+++ b/packages/SettingsLib/res/values-ro/strings.xml
@@ -582,8 +582,7 @@
<string name="time_unit_just_now" msgid="3006134267292728099">"Chiar acum"</string>
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Acest telefon"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Această tabletă"</string>
- <!-- no translation found for media_transfer_this_device_name_desktop (7912386128141470452) -->
- <skip />
+ <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Acest computer (intern)"</string>
<!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
<skip />
<string name="media_transfer_internal_mic" msgid="797333824290228595">"Microfon (intern)"</string>
@@ -687,12 +686,9 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Dezactivat"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Activat"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Pentru ca modificarea să se aplice, trebuie să repornești dispozitivul. Repornește-l acum sau anulează."</string>
- <!-- no translation found for media_transfer_wired_headphone_name (8698668536022665254) -->
- <skip />
- <!-- no translation found for media_transfer_headphone_name (1131962659136578852) -->
- <skip />
- <!-- no translation found for media_transfer_usb_speaker_name (4736537022543593896) -->
- <skip />
+ <string name="media_transfer_wired_headphone_name" msgid="8698668536022665254">"Căști cu fir"</string>
+ <string name="media_transfer_headphone_name" msgid="1131962659136578852">"Căști"</string>
+ <string name="media_transfer_usb_speaker_name" msgid="4736537022543593896">"Difuzor USB"</string>
<string name="media_transfer_wired_device_mic_name" msgid="7417067197803840965">"Mufă pentru microfon"</string>
<string name="media_transfer_usb_device_mic_name" msgid="9189914846215516322">"Microfon USB"</string>
<string name="wifi_hotspot_switch_on_text" msgid="9212273118217786155">"Activat"</string>
diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml
index ddb35d6f2119..b1726498b8ce 100644
--- a/packages/SettingsLib/res/values-sk/strings.xml
+++ b/packages/SettingsLib/res/values-sk/strings.xml
@@ -582,8 +582,7 @@
<string name="time_unit_just_now" msgid="3006134267292728099">"Teraz"</string>
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Tento telefón"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Tento tablet"</string>
- <!-- no translation found for media_transfer_this_device_name_desktop (7912386128141470452) -->
- <skip />
+ <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Tento počítač (interný)"</string>
<!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
<skip />
<string name="media_transfer_internal_mic" msgid="797333824290228595">"Mikrofón (vnútorný)"</string>
@@ -687,12 +686,9 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Vypnuté"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Zapnuté"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Zmena sa prejaví až po reštarte zariadenia. Môžete ho teraz reštartovať alebo akciu zrušiť."</string>
- <!-- no translation found for media_transfer_wired_headphone_name (8698668536022665254) -->
- <skip />
- <!-- no translation found for media_transfer_headphone_name (1131962659136578852) -->
- <skip />
- <!-- no translation found for media_transfer_usb_speaker_name (4736537022543593896) -->
- <skip />
+ <string name="media_transfer_wired_headphone_name" msgid="8698668536022665254">"Slúchadlá s káblom"</string>
+ <string name="media_transfer_headphone_name" msgid="1131962659136578852">"Slúchadlá"</string>
+ <string name="media_transfer_usb_speaker_name" msgid="4736537022543593896">"Reproduktor s rozhraním USB"</string>
<string name="media_transfer_wired_device_mic_name" msgid="7417067197803840965">"Konektor mikrofónu"</string>
<string name="media_transfer_usb_device_mic_name" msgid="9189914846215516322">"Mikrofón USB"</string>
<string name="wifi_hotspot_switch_on_text" msgid="9212273118217786155">"Zapnúť"</string>
diff --git a/packages/SettingsLib/res/values-sq/strings.xml b/packages/SettingsLib/res/values-sq/strings.xml
index d99f2c8f8ef1..7c210637c360 100644
--- a/packages/SettingsLib/res/values-sq/strings.xml
+++ b/packages/SettingsLib/res/values-sq/strings.xml
@@ -582,8 +582,7 @@
<string name="time_unit_just_now" msgid="3006134267292728099">"Pikërisht tani"</string>
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Ky telefon"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Ky tablet"</string>
- <!-- no translation found for media_transfer_this_device_name_desktop (7912386128141470452) -->
- <skip />
+ <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Ky kompjuter (i brendshëm)"</string>
<!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
<skip />
<string name="media_transfer_internal_mic" msgid="797333824290228595">"Mikrofoni (i brendshëm)"</string>
@@ -687,12 +686,9 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Joaktiv"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Aktiv"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Pajisja jote duhet të riniset që ky ndryshim të zbatohet. Rinise tani ose anuloje."</string>
- <!-- no translation found for media_transfer_wired_headphone_name (8698668536022665254) -->
- <skip />
- <!-- no translation found for media_transfer_headphone_name (1131962659136578852) -->
- <skip />
- <!-- no translation found for media_transfer_usb_speaker_name (4736537022543593896) -->
- <skip />
+ <string name="media_transfer_wired_headphone_name" msgid="8698668536022665254">"Kufje me tela"</string>
+ <string name="media_transfer_headphone_name" msgid="1131962659136578852">"Kufje"</string>
+ <string name="media_transfer_usb_speaker_name" msgid="4736537022543593896">"Altoparlant me USB"</string>
<string name="media_transfer_wired_device_mic_name" msgid="7417067197803840965">"Fisha e mikrofonit"</string>
<string name="media_transfer_usb_device_mic_name" msgid="9189914846215516322">"Mikrofoni me USB"</string>
<string name="wifi_hotspot_switch_on_text" msgid="9212273118217786155">"Aktive"</string>
diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml
index 6b76f879d04f..c553c1d30125 100644
--- a/packages/SettingsLib/res/values-sv/strings.xml
+++ b/packages/SettingsLib/res/values-sv/strings.xml
@@ -582,8 +582,7 @@
<string name="time_unit_just_now" msgid="3006134267292728099">"Nyss"</string>
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Den här telefonen"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Den här surfplattan"</string>
- <!-- no translation found for media_transfer_this_device_name_desktop (7912386128141470452) -->
- <skip />
+ <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Den här datorn (intern)"</string>
<!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
<skip />
<string name="media_transfer_internal_mic" msgid="797333824290228595">"Mikrofon (inbyggd)"</string>
@@ -687,12 +686,9 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Inaktiverat"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Aktiverat"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Enheten måste startas om för att ändringen ska börja gälla. Starta om nu eller avbryt."</string>
- <!-- no translation found for media_transfer_wired_headphone_name (8698668536022665254) -->
- <skip />
- <!-- no translation found for media_transfer_headphone_name (1131962659136578852) -->
- <skip />
- <!-- no translation found for media_transfer_usb_speaker_name (4736537022543593896) -->
- <skip />
+ <string name="media_transfer_wired_headphone_name" msgid="8698668536022665254">"Hörlur med kabel"</string>
+ <string name="media_transfer_headphone_name" msgid="1131962659136578852">"Hörlur"</string>
+ <string name="media_transfer_usb_speaker_name" msgid="4736537022543593896">"USB-högtalare"</string>
<string name="media_transfer_wired_device_mic_name" msgid="7417067197803840965">"Mikrofonuttag"</string>
<string name="media_transfer_usb_device_mic_name" msgid="9189914846215516322">"USB-mikrofon"</string>
<string name="wifi_hotspot_switch_on_text" msgid="9212273118217786155">"På"</string>
diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml
index f113e6220b51..6890d550f133 100644
--- a/packages/SettingsLib/res/values-sw/strings.xml
+++ b/packages/SettingsLib/res/values-sw/strings.xml
@@ -582,8 +582,7 @@
<string name="time_unit_just_now" msgid="3006134267292728099">"Sasa hivi"</string>
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Simu hii"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Kishikwambi hiki"</string>
- <!-- no translation found for media_transfer_this_device_name_desktop (7912386128141470452) -->
- <skip />
+ <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Kompyuta hii (spika ya ndani)"</string>
<!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
<skip />
<string name="media_transfer_internal_mic" msgid="797333824290228595">"Maikrofoni (ya ndani)"</string>
@@ -687,12 +686,9 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Imezimwa"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Imewashwa"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Ni lazima uwashe tena kifaa chako ili mabadiliko haya yatekelezwe. Washa tena sasa au ughairi."</string>
- <!-- no translation found for media_transfer_wired_headphone_name (8698668536022665254) -->
- <skip />
- <!-- no translation found for media_transfer_headphone_name (1131962659136578852) -->
- <skip />
- <!-- no translation found for media_transfer_usb_speaker_name (4736537022543593896) -->
- <skip />
+ <string name="media_transfer_wired_headphone_name" msgid="8698668536022665254">"Kipokea sauti cha kichwani chenye waya"</string>
+ <string name="media_transfer_headphone_name" msgid="1131962659136578852">"Kipokea sauti cha kichwani"</string>
+ <string name="media_transfer_usb_speaker_name" msgid="4736537022543593896">"Spika ya USB"</string>
<string name="media_transfer_wired_device_mic_name" msgid="7417067197803840965">"Pini ya maikrofoni"</string>
<string name="media_transfer_usb_device_mic_name" msgid="9189914846215516322">"Maikrofoni ya USB"</string>
<string name="wifi_hotspot_switch_on_text" msgid="9212273118217786155">"Umewashwa"</string>
diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml
index beca7380b007..5664290b0061 100644
--- a/packages/SettingsLib/res/values-ta/strings.xml
+++ b/packages/SettingsLib/res/values-ta/strings.xml
@@ -582,8 +582,7 @@
<string name="time_unit_just_now" msgid="3006134267292728099">"சற்றுமுன்"</string>
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"இந்த மொபைல்"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"இந்த டேப்லெட்"</string>
- <!-- no translation found for media_transfer_this_device_name_desktop (7912386128141470452) -->
- <skip />
+ <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"இந்தக் கம்ப்யூட்டர் (அகம்)"</string>
<!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
<skip />
<string name="media_transfer_internal_mic" msgid="797333824290228595">"மைக்ரோஃபோன் (அகம்)"</string>
@@ -687,12 +686,9 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"முடக்கப்பட்டது"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"இயக்கப்பட்டது"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"இந்த மாற்றங்கள் செயல்படுத்தப்பட உங்கள் சாதனத்தை மறுபடி தொடங்க வேண்டும். இப்போதே மறுபடி தொடங்கவும் அல்லது ரத்துசெய்யவும்."</string>
- <!-- no translation found for media_transfer_wired_headphone_name (8698668536022665254) -->
- <skip />
- <!-- no translation found for media_transfer_headphone_name (1131962659136578852) -->
- <skip />
- <!-- no translation found for media_transfer_usb_speaker_name (4736537022543593896) -->
- <skip />
+ <string name="media_transfer_wired_headphone_name" msgid="8698668536022665254">"வயர்டு ஹெட்ஃபோன்"</string>
+ <string name="media_transfer_headphone_name" msgid="1131962659136578852">"ஹெட்ஃபோன்"</string>
+ <string name="media_transfer_usb_speaker_name" msgid="4736537022543593896">"USB ஸ்பீக்கர்"</string>
<string name="media_transfer_wired_device_mic_name" msgid="7417067197803840965">"மைக் ஜாக்"</string>
<string name="media_transfer_usb_device_mic_name" msgid="9189914846215516322">"USB மைக்"</string>
<string name="wifi_hotspot_switch_on_text" msgid="9212273118217786155">"ஆன்"</string>
diff --git a/packages/SettingsLib/res/values-tr/strings.xml b/packages/SettingsLib/res/values-tr/strings.xml
index d7ad6b1a3184..14c2108a134f 100644
--- a/packages/SettingsLib/res/values-tr/strings.xml
+++ b/packages/SettingsLib/res/values-tr/strings.xml
@@ -582,8 +582,7 @@
<string name="time_unit_just_now" msgid="3006134267292728099">"Az önce"</string>
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Bu telefon"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Bu tablet"</string>
- <!-- no translation found for media_transfer_this_device_name_desktop (7912386128141470452) -->
- <skip />
+ <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Bu bilgisayar (dahili)"</string>
<!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
<skip />
<string name="media_transfer_internal_mic" msgid="797333824290228595">"Mikrofon (dahili)"</string>
@@ -687,12 +686,9 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Devre dışı"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Etkin"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Bu değişikliğin geçerli olması için cihazınızın yeniden başlatılması gerekir. Şimdi yeniden başlatın veya iptal edin."</string>
- <!-- no translation found for media_transfer_wired_headphone_name (8698668536022665254) -->
- <skip />
- <!-- no translation found for media_transfer_headphone_name (1131962659136578852) -->
- <skip />
- <!-- no translation found for media_transfer_usb_speaker_name (4736537022543593896) -->
- <skip />
+ <string name="media_transfer_wired_headphone_name" msgid="8698668536022665254">"Kablolu kulaklık"</string>
+ <string name="media_transfer_headphone_name" msgid="1131962659136578852">"Kulaklık"</string>
+ <string name="media_transfer_usb_speaker_name" msgid="4736537022543593896">"USB hoparlör"</string>
<string name="media_transfer_wired_device_mic_name" msgid="7417067197803840965">"Mikrofon jakı"</string>
<string name="media_transfer_usb_device_mic_name" msgid="9189914846215516322">"USB mikrofon"</string>
<string name="wifi_hotspot_switch_on_text" msgid="9212273118217786155">"Açık"</string>
diff --git a/packages/SettingsLib/res/values-uk/strings.xml b/packages/SettingsLib/res/values-uk/strings.xml
index 3ccbcf2d4205..25475d573352 100644
--- a/packages/SettingsLib/res/values-uk/strings.xml
+++ b/packages/SettingsLib/res/values-uk/strings.xml
@@ -582,8 +582,7 @@
<string name="time_unit_just_now" msgid="3006134267292728099">"Щойно"</string>
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Цей телефон"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Цей планшет"</string>
- <!-- no translation found for media_transfer_this_device_name_desktop (7912386128141470452) -->
- <skip />
+ <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Цей комп’ютер (внутрішній)"</string>
<!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
<skip />
<string name="media_transfer_internal_mic" msgid="797333824290228595">"Мікрофон (внутрішній)"</string>
@@ -687,12 +686,9 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Вимкнено"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Увімкнено"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Щоб застосувати ці зміни, потрібний перезапуск. Перезапустіть пристрій або скасуйте зміни."</string>
- <!-- no translation found for media_transfer_wired_headphone_name (8698668536022665254) -->
- <skip />
- <!-- no translation found for media_transfer_headphone_name (1131962659136578852) -->
- <skip />
- <!-- no translation found for media_transfer_usb_speaker_name (4736537022543593896) -->
- <skip />
+ <string name="media_transfer_wired_headphone_name" msgid="8698668536022665254">"Дротові навушники"</string>
+ <string name="media_transfer_headphone_name" msgid="1131962659136578852">"Навушники"</string>
+ <string name="media_transfer_usb_speaker_name" msgid="4736537022543593896">"USB-динамік"</string>
<string name="media_transfer_wired_device_mic_name" msgid="7417067197803840965">"Гніздо для мікрофона"</string>
<string name="media_transfer_usb_device_mic_name" msgid="9189914846215516322">"USB-мікрофон"</string>
<string name="wifi_hotspot_switch_on_text" msgid="9212273118217786155">"Увімкнено"</string>
diff --git a/packages/SettingsLib/res/values-uz/strings.xml b/packages/SettingsLib/res/values-uz/strings.xml
index 40d1d10f234b..71fdae2a7714 100644
--- a/packages/SettingsLib/res/values-uz/strings.xml
+++ b/packages/SettingsLib/res/values-uz/strings.xml
@@ -582,8 +582,7 @@
<string name="time_unit_just_now" msgid="3006134267292728099">"Hozir"</string>
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Shu telefon"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Shu planshet"</string>
- <!-- no translation found for media_transfer_this_device_name_desktop (7912386128141470452) -->
- <skip />
+ <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Bu kompyuter (ichki)"</string>
<!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
<skip />
<string name="media_transfer_internal_mic" msgid="797333824290228595">"Mikrofon (ichki)"</string>
@@ -687,12 +686,9 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Oʻchiq"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Yoniq"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Oʻzgarishlar kuchga kirishi uchun qurilmani oʻchirib yoqing. Buni hozir yoki keyinroq bajarishingiz mumkin."</string>
- <!-- no translation found for media_transfer_wired_headphone_name (8698668536022665254) -->
- <skip />
- <!-- no translation found for media_transfer_headphone_name (1131962659136578852) -->
- <skip />
- <!-- no translation found for media_transfer_usb_speaker_name (4736537022543593896) -->
- <skip />
+ <string name="media_transfer_wired_headphone_name" msgid="8698668536022665254">"Simli quloqlik"</string>
+ <string name="media_transfer_headphone_name" msgid="1131962659136578852">"Quloqlik"</string>
+ <string name="media_transfer_usb_speaker_name" msgid="4736537022543593896">"USB karnay"</string>
<string name="media_transfer_wired_device_mic_name" msgid="7417067197803840965">"Mikrofon ulagichi"</string>
<string name="media_transfer_usb_device_mic_name" msgid="9189914846215516322">"USB mik"</string>
<string name="wifi_hotspot_switch_on_text" msgid="9212273118217786155">"Yoniq"</string>
diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml
index 874aa18dc701..f53e8166c1e1 100644
--- a/packages/SettingsLib/res/values-vi/strings.xml
+++ b/packages/SettingsLib/res/values-vi/strings.xml
@@ -582,8 +582,7 @@
<string name="time_unit_just_now" msgid="3006134267292728099">"Vừa xong"</string>
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Điện thoại này"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Máy tính bảng này"</string>
- <!-- no translation found for media_transfer_this_device_name_desktop (7912386128141470452) -->
- <skip />
+ <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Máy tính này (nội bộ)"</string>
<!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
<skip />
<string name="media_transfer_internal_mic" msgid="797333824290228595">"Micrô (bên trong)"</string>
@@ -687,12 +686,9 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Đã tắt"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Đã bật"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Bạn phải khởi động lại thiết bị để áp dụng sự thay đổi này. Hãy khởi động lại ngay hoặc hủy."</string>
- <!-- no translation found for media_transfer_wired_headphone_name (8698668536022665254) -->
- <skip />
- <!-- no translation found for media_transfer_headphone_name (1131962659136578852) -->
- <skip />
- <!-- no translation found for media_transfer_usb_speaker_name (4736537022543593896) -->
- <skip />
+ <string name="media_transfer_wired_headphone_name" msgid="8698668536022665254">"Tai nghe có dây"</string>
+ <string name="media_transfer_headphone_name" msgid="1131962659136578852">"Tai nghe"</string>
+ <string name="media_transfer_usb_speaker_name" msgid="4736537022543593896">"Loa USB"</string>
<string name="media_transfer_wired_device_mic_name" msgid="7417067197803840965">"Giắc cắm micrô"</string>
<string name="media_transfer_usb_device_mic_name" msgid="9189914846215516322">"Micrô có cổng USB"</string>
<string name="wifi_hotspot_switch_on_text" msgid="9212273118217786155">"Đang bật"</string>
diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml
index 8edc656b4614..9275f1beed73 100644
--- a/packages/SettingsLib/res/values-zh-rCN/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml
@@ -582,8 +582,7 @@
<string name="time_unit_just_now" msgid="3006134267292728099">"刚刚"</string>
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"这部手机"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"这部平板电脑"</string>
- <!-- no translation found for media_transfer_this_device_name_desktop (7912386128141470452) -->
- <skip />
+ <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"此计算机(内部)"</string>
<!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
<skip />
<string name="media_transfer_internal_mic" msgid="797333824290228595">"麦克风(内部)"</string>
@@ -687,12 +686,9 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"已停用"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"已启用"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"设备必须重新启动才能应用此更改。您可以立即重新启动或取消。"</string>
- <!-- no translation found for media_transfer_wired_headphone_name (8698668536022665254) -->
- <skip />
- <!-- no translation found for media_transfer_headphone_name (1131962659136578852) -->
- <skip />
- <!-- no translation found for media_transfer_usb_speaker_name (4736537022543593896) -->
- <skip />
+ <string name="media_transfer_wired_headphone_name" msgid="8698668536022665254">"有线耳机"</string>
+ <string name="media_transfer_headphone_name" msgid="1131962659136578852">"头戴式耳机"</string>
+ <string name="media_transfer_usb_speaker_name" msgid="4736537022543593896">"USB 音箱"</string>
<string name="media_transfer_wired_device_mic_name" msgid="7417067197803840965">"麦克风插孔"</string>
<string name="media_transfer_usb_device_mic_name" msgid="9189914846215516322">"USB 麦克风"</string>
<string name="wifi_hotspot_switch_on_text" msgid="9212273118217786155">"开启"</string>
diff --git a/packages/SettingsLib/res/values-zh-rHK/strings.xml b/packages/SettingsLib/res/values-zh-rHK/strings.xml
index 736a30d0d169..4acc52ddc7b9 100644
--- a/packages/SettingsLib/res/values-zh-rHK/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rHK/strings.xml
@@ -582,8 +582,7 @@
<string name="time_unit_just_now" msgid="3006134267292728099">"剛剛"</string>
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"此手機"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"此平板電腦"</string>
- <!-- no translation found for media_transfer_this_device_name_desktop (7912386128141470452) -->
- <skip />
+ <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"此電腦 (內置)"</string>
<!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
<skip />
<string name="media_transfer_internal_mic" msgid="797333824290228595">"麥克風 (內置)"</string>
@@ -687,12 +686,9 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"已停用"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"已啟用"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"你的裝置必須重新開機,才能套用此變更。請立即重新開機或取消。"</string>
- <!-- no translation found for media_transfer_wired_headphone_name (8698668536022665254) -->
- <skip />
- <!-- no translation found for media_transfer_headphone_name (1131962659136578852) -->
- <skip />
- <!-- no translation found for media_transfer_usb_speaker_name (4736537022543593896) -->
- <skip />
+ <string name="media_transfer_wired_headphone_name" msgid="8698668536022665254">"有線耳機"</string>
+ <string name="media_transfer_headphone_name" msgid="1131962659136578852">"耳機"</string>
+ <string name="media_transfer_usb_speaker_name" msgid="4736537022543593896">"USB 喇叭"</string>
<string name="media_transfer_wired_device_mic_name" msgid="7417067197803840965">"麥克風插孔"</string>
<string name="media_transfer_usb_device_mic_name" msgid="9189914846215516322">"USB 麥克風"</string>
<string name="wifi_hotspot_switch_on_text" msgid="9212273118217786155">"開啟"</string>
diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml
index 692b6e106b23..8ded5d08edce 100644
--- a/packages/SettingsLib/res/values-zh-rTW/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml
@@ -582,8 +582,7 @@
<string name="time_unit_just_now" msgid="3006134267292728099">"剛剛"</string>
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"這支手機"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"這台平板電腦"</string>
- <!-- no translation found for media_transfer_this_device_name_desktop (7912386128141470452) -->
- <skip />
+ <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"這部電腦 (內部)"</string>
<!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
<skip />
<string name="media_transfer_internal_mic" msgid="797333824290228595">"麥克風 (內部)"</string>
@@ -687,12 +686,9 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"已停用"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"已啟用"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"裝置必須重新啟動才能套用這項變更。請立即重新啟動或取消變更。"</string>
- <!-- no translation found for media_transfer_wired_headphone_name (8698668536022665254) -->
- <skip />
- <!-- no translation found for media_transfer_headphone_name (1131962659136578852) -->
- <skip />
- <!-- no translation found for media_transfer_usb_speaker_name (4736537022543593896) -->
- <skip />
+ <string name="media_transfer_wired_headphone_name" msgid="8698668536022665254">"有線耳機"</string>
+ <string name="media_transfer_headphone_name" msgid="1131962659136578852">"耳機"</string>
+ <string name="media_transfer_usb_speaker_name" msgid="4736537022543593896">"USB 喇叭"</string>
<string name="media_transfer_wired_device_mic_name" msgid="7417067197803840965">"麥克風插孔"</string>
<string name="media_transfer_usb_device_mic_name" msgid="9189914846215516322">"USB 麥克風"</string>
<string name="wifi_hotspot_switch_on_text" msgid="9212273118217786155">"開啟"</string>
diff --git a/packages/SettingsLib/res/values-zu/strings.xml b/packages/SettingsLib/res/values-zu/strings.xml
index 4f0acbd292f6..972eed7be319 100644
--- a/packages/SettingsLib/res/values-zu/strings.xml
+++ b/packages/SettingsLib/res/values-zu/strings.xml
@@ -582,8 +582,7 @@
<string name="time_unit_just_now" msgid="3006134267292728099">"Khona manje"</string>
<string name="media_transfer_this_device_name" msgid="2357329267148436433">"Le foni"</string>
<string name="media_transfer_this_device_name_tablet" msgid="2975593806278422086">"Le thebhulethi"</string>
- <!-- no translation found for media_transfer_this_device_name_desktop (7912386128141470452) -->
- <skip />
+ <string name="media_transfer_this_device_name_desktop" msgid="7912386128141470452">"Le khompyutha (ngaphakathi)"</string>
<!-- no translation found for media_transfer_this_device_name_tv (5285685336836896535) -->
<skip />
<string name="media_transfer_internal_mic" msgid="797333824290228595">"Imakrofoni (okwangaphakathi)"</string>
@@ -687,12 +686,9 @@
<string name="cached_apps_freezer_disabled" msgid="4816382260660472042">"Ikhutshaziwe"</string>
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Inikwe amandla"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Kufanele idivayisi yakho iqaliswe ukuze lolu shintsho lusebenze. Qalisa manje noma khansela."</string>
- <!-- no translation found for media_transfer_wired_headphone_name (8698668536022665254) -->
- <skip />
- <!-- no translation found for media_transfer_headphone_name (1131962659136578852) -->
- <skip />
- <!-- no translation found for media_transfer_usb_speaker_name (4736537022543593896) -->
- <skip />
+ <string name="media_transfer_wired_headphone_name" msgid="8698668536022665254">"Amahedifoni anentambo"</string>
+ <string name="media_transfer_headphone_name" msgid="1131962659136578852">"Amahedifoni"</string>
+ <string name="media_transfer_usb_speaker_name" msgid="4736537022543593896">"Isipikha se-USB"</string>
<string name="media_transfer_wired_device_mic_name" msgid="7417067197803840965">"Umgodi we-earphone ye-mic"</string>
<string name="media_transfer_usb_device_mic_name" msgid="9189914846215516322">"I-mic ye-USB"</string>
<string name="wifi_hotspot_switch_on_text" msgid="9212273118217786155">"Vuliwe"</string>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 34e33c0df8f5..8b9ec389e3fd 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1409,12 +1409,10 @@
<string name="media_transfer_this_device_name">This phone</string>
<!-- Name of the tablet device. [CHAR LIMIT=30] -->
<string name="media_transfer_this_device_name_tablet">This tablet</string>
- <!-- Name of the internal speaker. [CHAR LIMIT=30] -->
+ <!-- Name of the internal speaker and mic. [CHAR LIMIT=30] -->
<string name="media_transfer_this_device_name_desktop">This computer (internal)</string>
<!-- Name of the default media output of the TV. [CHAR LIMIT=30] -->
<string name="media_transfer_this_device_name_tv">@string/tv_media_transfer_default</string>
- <!-- Name of the internal mic. [CHAR LIMIT=30] -->
- <string name="media_transfer_internal_mic">Microphone (internal)</string>
<!-- Name of the dock device. [CHAR LIMIT=30] -->
<string name="media_transfer_dock_speaker_device_name">Dock speaker</string>
<!-- Default name of the external device. [CHAR LIMIT=30] -->
@@ -1645,13 +1643,13 @@
<string name="media_transfer_headphone_name">Headphone</string>
<!-- Name of the usb audio device speaker, used in desktop devices. [CHAR LIMIT=50] -->
- <string name="media_transfer_usb_speaker_name">USB speaker</string>
+ <string name="media_transfer_usb_audio_name">USB audio</string>
<!-- Name of the 3.5mm audio device mic. [CHAR LIMIT=50] -->
<string name="media_transfer_wired_device_mic_name">Mic jack</string>
<!-- Name of the usb audio device mic. [CHAR LIMIT=50] -->
- <string name="media_transfer_usb_device_mic_name">USB mic</string>
+ <string name="media_transfer_usb_device_mic_name">USB microphone</string>
<!-- Label for Wifi hotspot switch on. Toggles hotspot on [CHAR LIMIT=30] -->
<string name="wifi_hotspot_switch_on_text">On</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index 616ab072ae20..612c193da9c3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
@@ -709,6 +709,9 @@ public class BluetoothUtils {
@WorkerThread
public static boolean hasConnectedBroadcastSourceForBtDevice(
@Nullable BluetoothDevice device, @Nullable LocalBluetoothManager localBtManager) {
+ if (Flags.audioSharingHysteresisModeFix()) {
+ return hasActiveLocalBroadcastSourceForBtDevice(device, localBtManager);
+ }
LocalBluetoothLeBroadcastAssistant assistant =
localBtManager == null
? null
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
index a3f9e515a0bc..364e95c61ca8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
@@ -52,6 +52,7 @@ import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import com.android.settingslib.R;
+import com.android.settingslib.flags.Flags;
import com.google.common.collect.ImmutableList;
@@ -1134,20 +1135,8 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded due to assistant profile is null");
return;
}
- List<BluetoothDevice> connectedDevices = mServiceBroadcastAssistant.getConnectedDevices();
- List<BluetoothDevice> devicesInSharing =
- connectedDevices.stream()
- .filter(
- bluetoothDevice -> {
- List<BluetoothLeBroadcastReceiveState> sourceList =
- mServiceBroadcastAssistant.getAllSources(
- bluetoothDevice);
- return !sourceList.isEmpty()
- && sourceList.stream()
- .anyMatch(BluetoothUtils::isConnected);
- })
- .collect(Collectors.toList());
- if (devicesInSharing.isEmpty()) {
+ List<BluetoothDevice> devicesInBroadcast = getDevicesInBroadcast();
+ if (devicesInBroadcast.isEmpty()) {
Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded due to no sinks in broadcast");
return;
}
@@ -1156,7 +1145,7 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
BluetoothDevice targetDevice = null;
// Find the earliest connected device in sharing session.
int targetDeviceIdx = -1;
- for (BluetoothDevice device : devicesInSharing) {
+ for (BluetoothDevice device : devicesInBroadcast) {
if (devices.contains(device)) {
int idx = devices.indexOf(device);
if (idx > targetDeviceIdx) {
@@ -1169,10 +1158,6 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded, target is null");
return;
}
- Log.d(
- TAG,
- "updateFallbackActiveDeviceIfNeeded, set active device: "
- + targetDevice.getAnonymizedAddress());
CachedBluetoothDevice targetCachedDevice = mDeviceManager.findDevice(targetDevice);
if (targetCachedDevice == null) {
Log.d(TAG, "Skip updateFallbackActiveDeviceIfNeeded, fail to find cached bt device");
@@ -1180,16 +1165,37 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
}
int fallbackActiveGroupId = getFallbackActiveGroupId();
if (fallbackActiveGroupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID
- && getGroupId(targetCachedDevice) == fallbackActiveGroupId) {
+ && BluetoothUtils.getGroupId(targetCachedDevice) == fallbackActiveGroupId) {
Log.d(
TAG,
"Skip updateFallbackActiveDeviceIfNeeded, already is fallback: "
+ fallbackActiveGroupId);
return;
}
+ Log.d(
+ TAG,
+ "updateFallbackActiveDeviceIfNeeded, set active device: "
+ + targetDevice.getAnonymizedAddress());
targetCachedDevice.setActive();
}
+ private List<BluetoothDevice> getDevicesInBroadcast() {
+ boolean hysteresisModeFixEnabled = Flags.audioSharingHysteresisModeFix();
+ List<BluetoothDevice> connectedDevices = mServiceBroadcastAssistant.getConnectedDevices();
+ return connectedDevices.stream()
+ .filter(
+ bluetoothDevice -> {
+ List<BluetoothLeBroadcastReceiveState> sourceList =
+ mServiceBroadcastAssistant.getAllSources(
+ bluetoothDevice);
+ return !sourceList.isEmpty() && sourceList.stream().anyMatch(
+ source -> hysteresisModeFixEnabled
+ ? BluetoothUtils.isSourceMatched(source, mBroadcastId)
+ : BluetoothUtils.isConnected(source));
+ })
+ .collect(Collectors.toList());
+ }
+
private int getFallbackActiveGroupId() {
return Settings.Secure.getInt(
mContext.getContentResolver(),
@@ -1197,23 +1203,6 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
BluetoothCsipSetCoordinator.GROUP_ID_INVALID);
}
- private int getGroupId(CachedBluetoothDevice cachedDevice) {
- int groupId = cachedDevice.getGroupId();
- String anonymizedAddress = cachedDevice.getDevice().getAnonymizedAddress();
- if (groupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) {
- Log.d(TAG, "getGroupId by CSIP profile for device: " + anonymizedAddress);
- return groupId;
- }
- for (LocalBluetoothProfile profile : cachedDevice.getProfiles()) {
- if (profile instanceof LeAudioProfile) {
- Log.d(TAG, "getGroupId by LEA profile for device: " + anonymizedAddress);
- return ((LeAudioProfile) profile).getGroupId(cachedDevice.getDevice());
- }
- }
- Log.d(TAG, "getGroupId return invalid id for device: " + anonymizedAddress);
- return BluetoothCsipSetCoordinator.GROUP_ID_INVALID;
- }
-
private void notifyBroadcastStateChange(@BroadcastState int state) {
if (!mContext.getPackageName().equals(SETTINGS_PKG)) {
Log.d(TAG, "Skip notifyBroadcastStateChange, not triggered by Settings.");
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingId.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingId.java
index 58dc8c7aad6c..e7c7476d4797 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingId.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingId.java
@@ -45,6 +45,7 @@ import java.lang.annotation.RetentionPolicy;
DeviceSettingId.DEVICE_SETTING_ID_KEYBOARD_SETTINGS,
DeviceSettingId.DEVICE_SETTING_ID_DEVICE_DETAILS_FOOTER,
DeviceSettingId.DEVICE_SETTING_ID_ANC,
+ DeviceSettingId.DEVICE_SETTING_ID_GENERAL_BLUETOOTH_DEVICE_HEADER,
},
open = true)
public @interface DeviceSettingId {
@@ -114,6 +115,9 @@ public @interface DeviceSettingId {
/** Device setting ID for "More Settings" page. */
int DEVICE_SETTING_ID_MORE_SETTINGS = 21;
+ /** Device setting ID for general bluetooth device header. */
+ int DEVICE_SETTING_ID_GENERAL_BLUETOOTH_DEVICE_HEADER = 22;
+
/** Device setting ID for ANC. */
int DEVICE_SETTING_ID_ANC = 1001;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingItem.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingItem.kt
index 38183d5a01fd..da01b3bcaafb 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingItem.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingItem.kt
@@ -32,9 +32,9 @@ import android.os.Parcelable
*/
data class DeviceSettingItem(
@DeviceSettingId val settingId: Int,
- val packageName: String,
- val className: String,
- val intentAction: String,
+ val packageName: String? = null,
+ val className: String? = null,
+ val intentAction: String? = null,
val preferenceKey: String? = null,
val highlighted: Boolean = false,
val extras: Bundle = Bundle.EMPTY,
@@ -62,11 +62,11 @@ data class DeviceSettingItem(
parcel.run {
DeviceSettingItem(
settingId = readInt(),
- packageName = readString() ?: "",
- className = readString() ?: "",
- intentAction = readString() ?: "",
+ packageName = readString(),
+ className = readString(),
+ intentAction = readString(),
highlighted = readBoolean(),
- preferenceKey = readString() ?: "",
+ preferenceKey = readString(),
extras = readBundle((Bundle::class.java.classLoader)) ?: Bundle.EMPTY,
)
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfig.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfig.kt
index 5656f38a0a11..36276696e3ec 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfig.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfig.kt
@@ -63,7 +63,8 @@ data class DeviceSettingsConfig(
},
moreSettingsHelpItem = readParcelable(
DeviceSettingItem::class.java.classLoader
- )
+ ),
+ extras = readBundle((Bundle::class.java.classLoader)) ?: Bundle.EMPTY,
)
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfigServiceStatus.aidl b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfigServiceStatus.aidl
new file mode 100644
index 000000000000..d8378067b115
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfigServiceStatus.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.bluetooth.devicesettings;
+
+parcelable DeviceSettingsConfigServiceStatus; \ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfigServiceStatus.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfigServiceStatus.kt
new file mode 100644
index 000000000000..ae867713c831
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfigServiceStatus.kt
@@ -0,0 +1,60 @@
+/*
+ * 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.bluetooth.devicesettings
+
+import android.os.Bundle
+import android.os.Parcel
+import android.os.Parcelable
+
+/**
+ * A data class representing a device settings config service status.
+ *
+ * @property success Whether the status is succeed.
+ * @property extras Extra bundle
+ */
+data class DeviceSettingsConfigServiceStatus(
+ val success: Boolean,
+ val extras: Bundle = Bundle.EMPTY,
+) : Parcelable {
+
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(parcel: Parcel, flags: Int) {
+ parcel.run {
+ writeBoolean(success)
+ writeBundle(extras)
+ }
+ }
+
+ companion object {
+ @JvmField
+ val CREATOR: Parcelable.Creator<DeviceSettingsConfigServiceStatus> =
+ object : Parcelable.Creator<DeviceSettingsConfigServiceStatus> {
+ override fun createFromParcel(parcel: Parcel) =
+ parcel.run {
+ DeviceSettingsConfigServiceStatus(
+ success = readBoolean(),
+ extras = readBundle((Bundle::class.java.classLoader)) ?: Bundle.EMPTY,
+ )
+ }
+
+ override fun newArray(size: Int): Array<DeviceSettingsConfigServiceStatus?> {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsProviderServiceStatus.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsProviderServiceStatus.kt
index 977849e75556..77d790e7f773 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsProviderServiceStatus.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsProviderServiceStatus.kt
@@ -21,7 +21,7 @@ import android.os.Parcel
import android.os.Parcelable
/**
- * A data class representing a device settings item in bluetooth device details config.
+ * A data class representing a device settings provider service status.
*
* @property enabled Whether the service is enabled.
* @property extras Extra bundle
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/IDeviceSettingsConfigProviderService.aidl b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/IDeviceSettingsConfigProviderService.aidl
index 647611ed8ef4..9cf49070a62c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/IDeviceSettingsConfigProviderService.aidl
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/IDeviceSettingsConfigProviderService.aidl
@@ -17,8 +17,8 @@
package com.android.settingslib.bluetooth.devicesettings;
import com.android.settingslib.bluetooth.devicesettings.DeviceInfo;
-import com.android.settingslib.bluetooth.devicesettings.DeviceSettingsConfig;
+import com.android.settingslib.bluetooth.devicesettings.IGetDeviceSettingsConfigCallback;
interface IDeviceSettingsConfigProviderService {
- DeviceSettingsConfig getDeviceSettingsConfig(in DeviceInfo device);
+ oneway void getDeviceSettingsConfig(in DeviceInfo device, in IGetDeviceSettingsConfigCallback callback);
} \ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/IGetDeviceSettingsConfigCallback.aidl b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/IGetDeviceSettingsConfigCallback.aidl
new file mode 100644
index 000000000000..403cdd9e4d70
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/IGetDeviceSettingsConfigCallback.aidl
@@ -0,0 +1,24 @@
+/*
+ * 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.bluetooth.devicesettings;
+
+import com.android.settingslib.bluetooth.devicesettings.DeviceSettingsConfig;
+import com.android.settingslib.bluetooth.devicesettings.DeviceSettingsConfigServiceStatus;
+
+interface IGetDeviceSettingsConfigCallback {
+ oneway void onResult(in DeviceSettingsConfigServiceStatus status, in DeviceSettingsConfig config) = 0;
+} \ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt
index 3d8ff86c9377..4af0504bd73a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt
@@ -33,12 +33,15 @@ import com.android.settingslib.bluetooth.devicesettings.DeviceSettingId
import com.android.settingslib.bluetooth.devicesettings.DeviceSettingPreferenceState
import com.android.settingslib.bluetooth.devicesettings.DeviceSettingState
import com.android.settingslib.bluetooth.devicesettings.DeviceSettingsConfig
+import com.android.settingslib.bluetooth.devicesettings.DeviceSettingsConfigServiceStatus
import com.android.settingslib.bluetooth.devicesettings.IDeviceSettingsConfigProviderService
import com.android.settingslib.bluetooth.devicesettings.IDeviceSettingsListener
import com.android.settingslib.bluetooth.devicesettings.IDeviceSettingsProviderService
+import com.android.settingslib.bluetooth.devicesettings.IGetDeviceSettingsConfigCallback
import com.android.settingslib.bluetooth.devicesettings.data.model.ServiceConnectionStatus
import java.util.concurrent.ConcurrentHashMap
import kotlin.coroutines.CoroutineContext
+import kotlin.coroutines.resume
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -63,6 +66,7 @@ import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.plus
+import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withContext
@OptIn(ExperimentalCoroutinesApi::class)
@@ -74,22 +78,22 @@ class DeviceSettingServiceConnection(
private val backgroundCoroutineContext: CoroutineContext,
) {
data class EndPoint(
- private val packageName: String,
+ private val packageName: String?,
private val className: String?,
- private val intentAction: String,
+ private val intentAction: String?,
) {
- fun toIntent(): Intent =
- Intent().apply {
+ fun toIntent(): Intent? {
+ if (TextUtils.isEmpty(packageName) || TextUtils.isEmpty(intentAction)) {
+ return null
+ }
+ return Intent().apply {
if (className.isNullOrBlank()) {
setPackage(packageName)
} else {
- setClassName(packageName, className)
+ setClassName(packageName!!, className)
}
setAction(intentAction)
}
-
- fun isValid(): Boolean {
- return !TextUtils.isEmpty(packageName) && !TextUtils.isEmpty(intentAction)
}
}
@@ -126,8 +130,9 @@ class DeviceSettingServiceConnection(
when (it) {
is ServiceConnectionStatus.Connected ->
flowOf(
- it.service.getDeviceSettingsConfig(
- deviceInfo { setBluetoothAddress(cachedDevice.address) }
+ getDeviceSettingsConfigFromService(
+ deviceInfo { setBluetoothAddress(cachedDevice.address) },
+ it.service,
)
)
ServiceConnectionStatus.Connecting -> flowOf()
@@ -137,6 +142,27 @@ class DeviceSettingServiceConnection(
.first()
}
+ private suspend fun getDeviceSettingsConfigFromService(
+ deviceInfo: DeviceInfo,
+ service: IDeviceSettingsConfigProviderService,
+ ): DeviceSettingsConfig? = suspendCancellableCoroutine { continuation ->
+ service.getDeviceSettingsConfig(
+ deviceInfo,
+ object : IGetDeviceSettingsConfigCallback.Stub() {
+ override fun onResult(
+ status: DeviceSettingsConfigServiceStatus,
+ config: DeviceSettingsConfig?,
+ ) {
+ if (!status.success) {
+ continuation.resume(null)
+ } else {
+ continuation.resume(config)
+ }
+ }
+ },
+ )
+ }
+
private val settingIdToItemMapping =
flow {
if (!isServiceEnabled.await()) {
@@ -160,6 +186,12 @@ class DeviceSettingServiceConnection(
}
.shareIn(scope = coroutineScope, started = SharingStarted.WhileSubscribed(), replay = 1)
+ private val services =
+ ConcurrentHashMap<
+ EndPoint,
+ StateFlow<ServiceConnectionStatus<IDeviceSettingsProviderService>>,
+ >()
+
/** Gets [DeviceSettingsConfig] for the device, return null when failed. */
suspend fun getDeviceSettingsConfig(): DeviceSettingsConfig? {
if (!isServiceEnabled.await()) {
@@ -222,24 +254,23 @@ class DeviceSettingServiceConnection(
)
}
}
- ?.filter { it.isValid() }
?.distinct()
- ?.associateBy(
- { it },
- { endpoint ->
- services.computeIfAbsent(endpoint) {
- getService(
- endpoint.toIntent(),
- IDeviceSettingsProviderService.Stub::asInterface,
- )
- .stateIn(
- coroutineScope.plus(backgroundCoroutineContext),
- SharingStarted.WhileSubscribed(),
- ServiceConnectionStatus.Connecting,
- )
- }
- },
- )
+ ?.mapNotNull { endpoint ->
+ endpoint.toIntent()?.let { intent ->
+ Pair(
+ endpoint,
+ services.computeIfAbsent(endpoint) {
+ getService(intent, IDeviceSettingsProviderService.Stub::asInterface)
+ .stateIn(
+ coroutineScope.plus(backgroundCoroutineContext),
+ SharingStarted.WhileSubscribed(),
+ ServiceConnectionStatus.Connecting,
+ )
+ },
+ )
+ }
+ }
+ ?.toMap()
private fun getDeviceSettingsFromService(
cachedDevice: CachedBluetoothDevice,
@@ -320,11 +351,5 @@ class DeviceSettingServiceConnection(
const val CONFIG_SERVICE_PACKAGE_NAME = "DEVICE_SETTINGS_CONFIG_PACKAGE_NAME"
const val CONFIG_SERVICE_CLASS_NAME = "DEVICE_SETTINGS_CONFIG_CLASS"
const val CONFIG_SERVICE_INTENT_ACTION = "DEVICE_SETTINGS_CONFIG_ACTION"
-
- val services =
- ConcurrentHashMap<
- EndPoint,
- StateFlow<ServiceConnectionStatus<IDeviceSettingsProviderService>>,
- >()
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java b/packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java
index ef0f6cbc6ed9..13a06017abbc 100644
--- a/packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java
+++ b/packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java
@@ -42,6 +42,8 @@ import androidx.annotation.Nullable;
import com.android.settingslib.R;
import com.android.settingslib.Utils;
+import java.util.Objects;
+
/**
* Drawable displaying a mobile cell signal indicator.
*/
@@ -90,6 +92,10 @@ public class SignalDrawable extends DrawableWrapper {
private int mCurrentDot;
public SignalDrawable(Context context) {
+ this(context, new Handler());
+ }
+
+ public SignalDrawable(@NonNull Context context, @NonNull Handler handler) {
super(context.getDrawable(ICON_RES));
final String attributionPathString = context.getString(
com.android.internal.R.string.config_signalAttributionPath);
@@ -106,7 +112,7 @@ public class SignalDrawable extends DrawableWrapper {
mIntrinsicSize = context.getResources().getDimensionPixelSize(R.dimen.signal_icon_size);
mTransparentPaint.setColor(context.getColor(android.R.color.transparent));
mTransparentPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
- mHandler = new Handler();
+ mHandler = handler;
setDarkIntensity(0);
}
@@ -304,6 +310,17 @@ public class SignalDrawable extends DrawableWrapper {
| level;
}
+ @Override
+ public boolean equals(@Nullable Object other) {
+ return other instanceof SignalDrawable
+ && ((SignalDrawable) other).getLevel() == this.getLevel();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(getLevel());
+ }
+
/** Returns the state representing empty mobile signal with the given number of levels. */
public static int getEmptyState(int numLevels) {
return getState(0, numLevels, true);
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java
index dae69e64934c..e44a13491758 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java
@@ -98,14 +98,13 @@ public class InputMediaDevice extends MediaDevice {
@Override
public @NonNull String getName() {
- CharSequence name =
- switch (mAudioDeviceInfoType) {
- case TYPE_WIRED_HEADSET ->
- mContext.getString(R.string.media_transfer_wired_device_mic_name);
- case TYPE_USB_DEVICE, TYPE_USB_HEADSET, TYPE_USB_ACCESSORY ->
- mContext.getString(R.string.media_transfer_usb_device_mic_name);
- default -> mContext.getString(R.string.media_transfer_internal_mic);
- };
+ CharSequence name = switch (mAudioDeviceInfoType) {
+ case TYPE_WIRED_HEADSET -> mContext.getString(
+ R.string.media_transfer_wired_device_mic_name);
+ case TYPE_USB_DEVICE, TYPE_USB_HEADSET, TYPE_USB_ACCESSORY -> mContext.getString(
+ R.string.media_transfer_usb_device_mic_name);
+ default -> mContext.getString(R.string.media_transfer_this_device_name_desktop);
+ };
return name.toString();
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
index 0b8fb22cef3a..feaf7fbc4b64 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
@@ -97,7 +97,7 @@ public class PhoneMediaDevice extends MediaDevice {
case TYPE_USB_ACCESSORY:
name =
inputRoutingEnabledAndIsDesktop()
- ? context.getString(R.string.media_transfer_usb_speaker_name)
+ ? context.getString(R.string.media_transfer_usb_audio_name)
: context.getString(R.string.media_transfer_wired_headphone_name);
break;
case TYPE_DOCK:
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
index 8eedb35a3181..0e060dfdd447 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
@@ -47,6 +47,7 @@ import android.provider.Settings;
import android.util.Pair;
import com.android.internal.R;
+import com.android.settingslib.flags.Flags;
import com.android.settingslib.widget.AdaptiveIcon;
import com.google.common.collect.ImmutableList;
@@ -605,6 +606,7 @@ public class BluetoothUtilsTest {
@Test
public void testHasConnectedBroadcastSource_leadDeviceConnectedToBroadcastSource() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
CachedBluetoothDevice memberCachedDevice = mock(CachedBluetoothDevice.class);
BluetoothDevice memberDevice = mock(BluetoothDevice.class);
@@ -630,6 +632,7 @@ public class BluetoothUtilsTest {
@Test
public void testHasConnectedBroadcastSource_memberDeviceConnectedToBroadcastSource() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
CachedBluetoothDevice memberCachedDevice = mock(CachedBluetoothDevice.class);
BluetoothDevice memberDevice = mock(BluetoothDevice.class);
@@ -655,6 +658,7 @@ public class BluetoothUtilsTest {
@Test
public void testHasConnectedBroadcastSource_deviceNotConnectedToBroadcastSource() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
List<Long> bisSyncState = new ArrayList<>();
@@ -672,6 +676,7 @@ public class BluetoothUtilsTest {
@Test
public void testHasConnectedBroadcastSourceForBtDevice_deviceConnectedToBroadcastSource() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
List<Long> bisSyncState = new ArrayList<>();
bisSyncState.add(1L);
when(mLeBroadcastReceiveState.getBisSyncState()).thenReturn(bisSyncState);
@@ -688,6 +693,7 @@ public class BluetoothUtilsTest {
@Test
public void testHasConnectedBroadcastSourceForBtDevice_deviceNotConnectedToBroadcastSource() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
List<Long> bisSyncState = new ArrayList<>();
when(mLeBroadcastReceiveState.getBisSyncState()).thenReturn(bisSyncState);
@@ -702,6 +708,106 @@ public class BluetoothUtilsTest {
}
@Test
+ public void hasConnectedBroadcastSource_hysteresisFix_leadDeviceHasActiveLocalSource() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
+ when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+ CachedBluetoothDevice memberCachedDevice = mock(CachedBluetoothDevice.class);
+ BluetoothDevice memberDevice = mock(BluetoothDevice.class);
+ when(memberCachedDevice.getDevice()).thenReturn(memberDevice);
+ Set<CachedBluetoothDevice> memberCachedDevices = new HashSet<>();
+ memberCachedDevices.add(memberCachedDevice);
+ when(mCachedBluetoothDevice.getMemberDevice()).thenReturn(memberCachedDevices);
+
+
+ when(mBroadcast.getLatestBroadcastId()).thenReturn(TEST_BROADCAST_ID);
+ when(mLeBroadcastReceiveState.getBroadcastId()).thenReturn(TEST_BROADCAST_ID);
+
+ List<BluetoothLeBroadcastReceiveState> sourceList = new ArrayList<>();
+ sourceList.add(mLeBroadcastReceiveState);
+ when(mAssistant.getAllSources(mBluetoothDevice)).thenReturn(sourceList);
+ when(mAssistant.getAllSources(memberDevice)).thenReturn(Collections.emptyList());
+
+ assertThat(
+ BluetoothUtils.hasConnectedBroadcastSource(
+ mCachedBluetoothDevice, mLocalBluetoothManager))
+ .isTrue();
+ }
+
+ @Test
+ public void hasConnectedBroadcastSource_hysteresisFix_memberDeviceHasActiveLocalSource() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
+ when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+ CachedBluetoothDevice memberCachedDevice = mock(CachedBluetoothDevice.class);
+ BluetoothDevice memberDevice = mock(BluetoothDevice.class);
+ when(memberCachedDevice.getDevice()).thenReturn(memberDevice);
+ Set<CachedBluetoothDevice> memberCachedDevices = new HashSet<>();
+ memberCachedDevices.add(memberCachedDevice);
+ when(mCachedBluetoothDevice.getMemberDevice()).thenReturn(memberCachedDevices);
+
+ when(mBroadcast.getLatestBroadcastId()).thenReturn(TEST_BROADCAST_ID);
+ when(mLeBroadcastReceiveState.getBroadcastId()).thenReturn(TEST_BROADCAST_ID);
+
+ List<BluetoothLeBroadcastReceiveState> sourceList = new ArrayList<>();
+ sourceList.add(mLeBroadcastReceiveState);
+ when(mAssistant.getAllSources(memberDevice)).thenReturn(sourceList);
+ when(mAssistant.getAllSources(mBluetoothDevice)).thenReturn(Collections.emptyList());
+
+ assertThat(
+ BluetoothUtils.hasConnectedBroadcastSource(
+ mCachedBluetoothDevice, mLocalBluetoothManager))
+ .isTrue();
+ }
+
+ @Test
+ public void hasConnectedBroadcastSource_hysteresisFix_deviceNoActiveLocalSource() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
+ when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+
+ when(mBroadcast.getLatestBroadcastId()).thenReturn(UNKNOWN_VALUE_PLACEHOLDER);
+
+ List<BluetoothLeBroadcastReceiveState> sourceList = new ArrayList<>();
+ sourceList.add(mLeBroadcastReceiveState);
+ when(mAssistant.getAllSources(mBluetoothDevice)).thenReturn(sourceList);
+
+ assertThat(
+ BluetoothUtils.hasConnectedBroadcastSource(
+ mCachedBluetoothDevice, mLocalBluetoothManager))
+ .isFalse();
+ }
+
+ @Test
+ public void hasConnectedBroadcastSourceForBtDevice_hysteresisFix_deviceHasActiveLocalSource() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
+ when(mBroadcast.getLatestBroadcastId()).thenReturn(TEST_BROADCAST_ID);
+ when(mLeBroadcastReceiveState.getBroadcastId()).thenReturn(TEST_BROADCAST_ID);
+
+ List<BluetoothLeBroadcastReceiveState> sourceList = new ArrayList<>();
+ sourceList.add(mLeBroadcastReceiveState);
+ when(mAssistant.getAllSources(mBluetoothDevice)).thenReturn(sourceList);
+
+ assertThat(
+ BluetoothUtils.hasConnectedBroadcastSourceForBtDevice(
+ mBluetoothDevice, mLocalBluetoothManager))
+ .isTrue();
+ }
+
+ @Test
+ public void hasConnectedBroadcastSourceForBtDevice_hysteresisFix_deviceNoActiveLocalSource() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_SHARING_HYSTERESIS_MODE_FIX);
+ when(mBroadcast.getLatestBroadcastId()).thenReturn(TEST_BROADCAST_ID);
+ when(mLeBroadcastReceiveState.getBroadcastId()).thenReturn(UNKNOWN_VALUE_PLACEHOLDER);
+
+ List<BluetoothLeBroadcastReceiveState> sourceList = new ArrayList<>();
+ sourceList.add(mLeBroadcastReceiveState);
+ when(mAssistant.getAllSources(mBluetoothDevice)).thenReturn(sourceList);
+
+ assertThat(
+ BluetoothUtils.hasConnectedBroadcastSourceForBtDevice(
+ mBluetoothDevice, mLocalBluetoothManager))
+ .isFalse();
+ }
+
+ @Test
public void testHasActiveLocalBroadcastSourceForBtDevice_hasActiveLocalSource() {
when(mBroadcast.getLatestBroadcastId()).thenReturn(TEST_BROADCAST_ID);
when(mLeBroadcastReceiveState.getBroadcastId()).thenReturn(TEST_BROADCAST_ID);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfigServiceStatusTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfigServiceStatusTest.kt
new file mode 100644
index 000000000000..3149acf6fc3c
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfigServiceStatusTest.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.settingslib.bluetooth.devicesettings
+
+import android.os.Bundle
+import android.os.Parcel
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+
+@RunWith(RobolectricTestRunner::class)
+class DeviceSettingsConfigServiceStatusTest {
+
+ @Test
+ fun parcelOperation() {
+ val item =
+ DeviceSettingsConfigServiceStatus(
+ success = true,
+ extras = Bundle().apply { putString("key1", "value1") },
+ )
+
+ val fromParcel = writeAndRead(item)
+
+ assertThat(fromParcel.success).isEqualTo(item.success)
+ assertThat(fromParcel.extras.getString("key1")).isEqualTo(item.extras.getString("key1"))
+ }
+
+ private fun writeAndRead(
+ item: DeviceSettingsConfigServiceStatus
+ ): DeviceSettingsConfigServiceStatus {
+ val parcel = Parcel.obtain()
+ item.writeToParcel(parcel, 0)
+ parcel.setDataPosition(0)
+ return DeviceSettingsConfigServiceStatus.CREATOR.createFromParcel(parcel)
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfigTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfigTest.kt
index 7f1729387a22..ebaad342f9f2 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfigTest.kt
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfigTest.kt
@@ -86,6 +86,7 @@ class DeviceSettingsConfigTest {
assertThat(fromParcel.moreSettingsHelpItem?.packageName).isEqualTo("package_name_2")
assertThat(fromParcel.moreSettingsHelpItem?.className).isEqualTo("class_name_2")
assertThat(fromParcel.moreSettingsHelpItem?.intentAction).isEqualTo("intent_action_2")
+ assertThat(fromParcel.extras.getString("key1")).isEqualTo("value1")
}
private fun writeAndRead(item: DeviceSettingsConfig): DeviceSettingsConfig {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt
index 0cb6bc1b1261..4e62fd3b27c5 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt
@@ -33,10 +33,12 @@ import com.android.settingslib.bluetooth.devicesettings.DeviceSettingId
import com.android.settingslib.bluetooth.devicesettings.DeviceSettingItem
import com.android.settingslib.bluetooth.devicesettings.DeviceSettingState
import com.android.settingslib.bluetooth.devicesettings.DeviceSettingsConfig
+import com.android.settingslib.bluetooth.devicesettings.DeviceSettingsConfigServiceStatus
import com.android.settingslib.bluetooth.devicesettings.DeviceSettingsProviderServiceStatus
import com.android.settingslib.bluetooth.devicesettings.IDeviceSettingsConfigProviderService
import com.android.settingslib.bluetooth.devicesettings.IDeviceSettingsListener
import com.android.settingslib.bluetooth.devicesettings.IDeviceSettingsProviderService
+import com.android.settingslib.bluetooth.devicesettings.IGetDeviceSettingsConfigCallback
import com.android.settingslib.bluetooth.devicesettings.MultiTogglePreference
import com.android.settingslib.bluetooth.devicesettings.MultiTogglePreferenceState
import com.android.settingslib.bluetooth.devicesettings.ToggleInfo
@@ -53,7 +55,6 @@ import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
-import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -140,15 +141,10 @@ class DeviceSettingRepositoryTest {
)
}
- @After
- fun clean() {
- DeviceSettingServiceConnection.services.clear()
- }
-
@Test
fun getDeviceSettingsConfig_withMetadata_success() {
testScope.runTest {
- `when`(configService.getDeviceSettingsConfig(any())).thenReturn(DEVICE_SETTING_CONFIG)
+ setUpConfigService(true, DEVICE_SETTING_CONFIG)
`when`(settingProviderService1.serviceStatus)
.thenReturn(DeviceSettingsProviderServiceStatus(true))
`when`(settingProviderService2.serviceStatus)
@@ -179,7 +175,7 @@ class DeviceSettingRepositoryTest {
)
)
.thenReturn("".toByteArray())
- `when`(configService.getDeviceSettingsConfig(any())).thenReturn(DEVICE_SETTING_CONFIG)
+ setUpConfigService(true, DEVICE_SETTING_CONFIG)
`when`(settingProviderService1.serviceStatus)
.thenReturn(DeviceSettingsProviderServiceStatus(true))
`when`(settingProviderService2.serviceStatus)
@@ -194,7 +190,7 @@ class DeviceSettingRepositoryTest {
@Test
fun getDeviceSettingsConfig_providerServiceNotEnabled_returnNull() {
testScope.runTest {
- `when`(configService.getDeviceSettingsConfig(any())).thenReturn(DEVICE_SETTING_CONFIG)
+ setUpConfigService(true, DEVICE_SETTING_CONFIG)
`when`(settingProviderService1.serviceStatus)
.thenReturn(DeviceSettingsProviderServiceStatus(false))
`when`(settingProviderService2.serviceStatus)
@@ -209,7 +205,7 @@ class DeviceSettingRepositoryTest {
@Test
fun getDeviceSettingsConfig_bindingServiceFail_returnNull() {
testScope.runTest {
- `when`(configService.getDeviceSettingsConfig(any())).thenReturn(DEVICE_SETTING_CONFIG)
+ setUpConfigService(true, DEVICE_SETTING_CONFIG)
doReturn(false).`when`(context).bindService(any(), anyInt(), any(), any())
val config = underTest.getDeviceSettingsConfig(cachedDevice)
@@ -221,7 +217,7 @@ class DeviceSettingRepositoryTest {
@Test
fun getDeviceSetting_actionSwitchPreference_success() {
testScope.runTest {
- `when`(configService.getDeviceSettingsConfig(any())).thenReturn(DEVICE_SETTING_CONFIG)
+ setUpConfigService(true, DEVICE_SETTING_CONFIG)
`when`(settingProviderService1.registerDeviceSettingsListener(any(), any())).then {
input ->
input
@@ -247,7 +243,7 @@ class DeviceSettingRepositoryTest {
@Test
fun getDeviceSetting_multiTogglePreference_success() {
testScope.runTest {
- `when`(configService.getDeviceSettingsConfig(any())).thenReturn(DEVICE_SETTING_CONFIG)
+ setUpConfigService(true, DEVICE_SETTING_CONFIG)
`when`(settingProviderService2.registerDeviceSettingsListener(any(), any())).then {
input ->
input
@@ -273,7 +269,7 @@ class DeviceSettingRepositoryTest {
@Test
fun getDeviceSetting_helpPreference_success() {
testScope.runTest {
- `when`(configService.getDeviceSettingsConfig(any())).thenReturn(DEVICE_SETTING_CONFIG)
+ setUpConfigService(true, DEVICE_SETTING_CONFIG)
`when`(settingProviderService2.registerDeviceSettingsListener(any(), any())).then {
input ->
input
@@ -299,6 +295,7 @@ class DeviceSettingRepositoryTest {
@Test
fun getDeviceSetting_noConfig_returnNull() {
testScope.runTest {
+ setUpConfigService(false, null)
`when`(settingProviderService1.registerDeviceSettingsListener(any(), any())).then {
input ->
input
@@ -320,7 +317,7 @@ class DeviceSettingRepositoryTest {
@Test
fun updateDeviceSettingState_switchState_success() {
testScope.runTest {
- `when`(configService.getDeviceSettingsConfig(any())).thenReturn(DEVICE_SETTING_CONFIG)
+ setUpConfigService(true, DEVICE_SETTING_CONFIG)
`when`(settingProviderService1.registerDeviceSettingsListener(any(), any())).then {
input ->
input
@@ -358,7 +355,7 @@ class DeviceSettingRepositoryTest {
@Test
fun updateDeviceSettingState_multiToggleState_success() {
testScope.runTest {
- `when`(configService.getDeviceSettingsConfig(any())).thenReturn(DEVICE_SETTING_CONFIG)
+ setUpConfigService(true, DEVICE_SETTING_CONFIG)
`when`(settingProviderService2.registerDeviceSettingsListener(any(), any())).then {
input ->
input
@@ -459,6 +456,17 @@ class DeviceSettingRepositoryTest {
assertThat(actual.settingId).isEqualTo(serviceResponse.settingId)
}
+ private fun setUpConfigService(success: Boolean, config: DeviceSettingsConfig?) {
+ `when`(configService.getDeviceSettingsConfig(any(), any())).then { input ->
+ input
+ .getArgument<IGetDeviceSettingsConfigCallback>(1)
+ .onResult(
+ DeviceSettingsConfigServiceStatus(success = success),
+ config
+ )
+ }
+ }
+
private companion object {
const val BLUETOOTH_ADDRESS = "12:34:56:78"
const val CONFIG_SERVICE_PACKAGE_NAME = "com.android.fake.configservice"
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputMediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputMediaDeviceTest.java
index 088d554326e7..2f0aa1cf0cb9 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputMediaDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputMediaDeviceTest.java
@@ -76,7 +76,7 @@ public class InputMediaDeviceTest {
IS_VOLUME_FIXED);
assertThat(builtinMediaDevice).isNotNull();
assertThat(builtinMediaDevice.getName())
- .isEqualTo(mContext.getString(R.string.media_transfer_internal_mic));
+ .isEqualTo(mContext.getString(R.string.media_transfer_this_device_name_desktop));
}
@Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java
index da5f428ce23b..1739c0e5e2bf 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/PhoneMediaDeviceTest.java
@@ -136,7 +136,7 @@ public class PhoneMediaDeviceTest {
when(mInfo.getType()).thenReturn(TYPE_USB_DEVICE);
assertThat(mPhoneMediaDevice.getName())
- .isEqualTo(mContext.getString(R.string.media_transfer_usb_speaker_name));
+ .isEqualTo(mContext.getString(R.string.media_transfer_usb_audio_name));
when(mInfo.getType()).thenReturn(TYPE_BUILTIN_SPEAKER);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AppPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AppPreferenceTest.java
deleted file mode 100644
index 6c8fd50d1896..000000000000
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AppPreferenceTest.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.widget;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.content.Context;
-import android.view.View;
-
-import androidx.preference.PreferenceViewHolder;
-
-import com.android.settingslib.widget.preference.app.R;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-
-@RunWith(RobolectricTestRunner.class)
-public class AppPreferenceTest {
-
- private Context mContext;
- private View mRootView;
- private AppPreference mPref;
- private PreferenceViewHolder mHolder;
-
- @Before
- public void setUp() {
- mContext = RuntimeEnvironment.application;
- mRootView = View.inflate(mContext, R.layout.preference_app, null /* parent */);
- mHolder = PreferenceViewHolder.createInstanceForTests(mRootView);
- mPref = new AppPreference(mContext);
- }
-
- @Test
- public void setProgress_showProgress() {
- mPref.setProgress(1);
- mPref.onBindViewHolder(mHolder);
-
- assertThat(mHolder.findViewById(android.R.id.progress).getVisibility())
- .isEqualTo(View.VISIBLE);
- }
-
- @Test
- public void foobar_testName() {
- float iconSize = mContext.getResources().getDimension(com.android.settingslib.widget.theme.R.dimen.secondary_app_icon_size);
- assertThat(Float.floatToIntBits(iconSize)).isEqualTo(Float.floatToIntBits(32));
- }
-}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index d7109398b956..5e31da411e49 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -164,6 +164,7 @@ public class SecureSettings {
Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS,
Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
+ Settings.Secure.LOCK_SCREEN_NOTIFICATION_MINIMALISM,
Settings.Secure.SHOW_NOTIFICATION_SNOOZE,
Settings.Secure.NOTIFICATION_HISTORY_ENABLED,
Settings.Secure.ZEN_DURATION,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index fa16a44f4592..b3f73749f393 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -243,6 +243,7 @@ public class SecureSettingsValidators {
VALIDATORS.put(Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Secure.LOCK_SCREEN_NOTIFICATION_MINIMALISM, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.SHOW_NOTIFICATION_SNOOZE, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.NOTIFICATION_HISTORY_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ZEN_DURATION, ANY_INTEGER_VALIDATOR);
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index f8383d94b1ab..cb1411bdd91e 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -40,16 +40,6 @@ flag {
}
flag {
- name: "notification_minimalism_prototype"
- namespace: "systemui"
- description: "Prototype of notification minimalism; the new 'Intermediate' lockscreen customization proposal."
- bug: "330387368"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "notification_view_flipper_pausing_v2"
namespace: "systemui"
description: "Pause ViewFlippers inside Notification custom layouts when the shade is closed."
@@ -569,6 +559,13 @@ flag {
}
flag {
+ name: "volume_redesign"
+ namespace: "systemui"
+ description: "Enables Volume BC25 visuals update"
+ bug: "368308908"
+}
+
+flag {
name: "clipboard_shared_transitions"
namespace: "systemui"
description: "Show shared transitions from clipboard"
@@ -1128,6 +1125,16 @@ flag {
}
flag {
+ name: "media_controls_umo_inflation_in_background"
+ namespace: "systemui"
+ description: "Inflate UMO in background thread"
+ bug: "368514198"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
namespace: "systemui"
name: "enable_view_capture_tracing"
description: "Enables view capture tracing in System UI."
@@ -1431,4 +1438,4 @@ flag {
metadata {
purpose: PURPOSE_BUGFIX
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginRemoteTransition.java b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginRemoteTransition.java
new file mode 100644
index 000000000000..08db95e5a795
--- /dev/null
+++ b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginRemoteTransition.java
@@ -0,0 +1,374 @@
+/*
+ * 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.animation;
+
+import android.animation.Animator;
+import android.animation.Animator.AnimatorListener;
+import android.animation.ValueAnimator;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.SurfaceControl;
+import android.window.IRemoteTransition;
+import android.window.IRemoteTransitionFinishedCallback;
+import android.window.TransitionInfo;
+import android.window.TransitionInfo.Change;
+import android.window.WindowAnimationState;
+
+import com.android.internal.policy.ScreenDecorationsUtils;
+import com.android.wm.shell.shared.TransitionUtil;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * An implementation of {@link IRemoteTransition} that accepts a {@link UIComponent} as the origin
+ * and automatically attaches it to the transition leash before the transition starts.
+ */
+public class OriginRemoteTransition extends IRemoteTransition.Stub {
+ private static final String TAG = "OriginRemoteTransition";
+
+ private final Context mContext;
+ private final boolean mIsEntry;
+ private final UIComponent mOrigin;
+ private final TransitionPlayer mPlayer;
+ private final long mDuration;
+ private final Handler mHandler;
+
+ @Nullable private SurfaceControl.Transaction mStartTransaction;
+ @Nullable private IRemoteTransitionFinishedCallback mFinishCallback;
+ @Nullable private UIComponent.Transaction mOriginTransaction;
+ @Nullable private ValueAnimator mAnimator;
+ @Nullable private SurfaceControl mOriginLeash;
+ private boolean mCancelled;
+
+ OriginRemoteTransition(
+ Context context,
+ boolean isEntry,
+ UIComponent origin,
+ TransitionPlayer player,
+ long duration,
+ Handler handler) {
+ mContext = context;
+ mIsEntry = isEntry;
+ mOrigin = origin;
+ mPlayer = player;
+ mDuration = duration;
+ mHandler = handler;
+ }
+
+ @Override
+ public void startAnimation(
+ IBinder token,
+ TransitionInfo info,
+ SurfaceControl.Transaction t,
+ IRemoteTransitionFinishedCallback finishCallback) {
+ logD("startAnimation - " + info);
+ mHandler.post(
+ () -> {
+ mStartTransaction = t;
+ mFinishCallback = finishCallback;
+ startAnimationInternal(info);
+ });
+ }
+
+ @Override
+ public void mergeAnimation(
+ IBinder transition,
+ TransitionInfo info,
+ SurfaceControl.Transaction t,
+ IBinder mergeTarget,
+ IRemoteTransitionFinishedCallback finishCallback) {
+ logD("mergeAnimation - " + info);
+ mHandler.post(this::cancel);
+ }
+
+ @Override
+ public void takeOverAnimation(
+ IBinder transition,
+ TransitionInfo info,
+ SurfaceControl.Transaction t,
+ IRemoteTransitionFinishedCallback finishCallback,
+ WindowAnimationState[] states) {
+ logD("takeOverAnimation - " + info);
+ }
+
+ @Override
+ public void onTransitionConsumed(IBinder transition, boolean aborted) {
+ logD("onTransitionConsumed - aborted: " + aborted);
+ mHandler.post(this::cancel);
+ }
+
+ private void startAnimationInternal(TransitionInfo info) {
+ if (!prepareUIs(info)) {
+ logE("Unable to prepare UI!");
+ finishAnimation(/* finished= */ false);
+ return;
+ }
+ // Notify player that we are starting.
+ mPlayer.onStart(info, mStartTransaction, mOrigin, mOriginTransaction);
+
+ // Start the animator.
+ mAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
+ mAnimator.setDuration(mDuration);
+ mAnimator.addListener(
+ new AnimatorListener() {
+ @Override
+ public void onAnimationStart(Animator a) {}
+
+ @Override
+ public void onAnimationEnd(Animator a) {
+ finishAnimation(/* finished= */ !mCancelled);
+ }
+
+ @Override
+ public void onAnimationCancel(Animator a) {
+ mCancelled = true;
+ }
+
+ @Override
+ public void onAnimationRepeat(Animator a) {}
+ });
+ mAnimator.addUpdateListener(
+ a -> {
+ mPlayer.onProgress((float) a.getAnimatedValue());
+ });
+ mAnimator.start();
+ }
+
+ private boolean prepareUIs(TransitionInfo info) {
+ if (info.getRootCount() == 0) {
+ logE("prepareUIs: no root leash!");
+ return false;
+ }
+ if (info.getRootCount() > 1) {
+ logE("prepareUIs: multi-display transition is not supported yet!");
+ return false;
+ }
+ if (info.getChanges().isEmpty()) {
+ logE("prepareUIs: no changes!");
+ return false;
+ }
+
+ SurfaceControl rootLeash = info.getRoot(0).getLeash();
+ int displayId = info.getChanges().get(0).getEndDisplayId();
+ Rect displayBounds = getDisplayBounds(displayId);
+ float windowRadius = ScreenDecorationsUtils.getWindowCornerRadius(mContext);
+ logD("prepareUIs: windowRadius=" + windowRadius + ", displayBounds=" + displayBounds);
+
+ // Create the origin leash and add to the transition root leash.
+ mOriginLeash =
+ new SurfaceControl.Builder().setName("OriginTransition-origin-leash").build();
+ mStartTransaction
+ .reparent(mOriginLeash, rootLeash)
+ .show(mOriginLeash)
+ .setCornerRadius(mOriginLeash, windowRadius)
+ .setWindowCrop(mOriginLeash, displayBounds.width(), displayBounds.height());
+
+ // Process surfaces
+ List<SurfaceControl> openingSurfaces = new ArrayList<>();
+ List<SurfaceControl> closingSurfaces = new ArrayList<>();
+ for (Change change : info.getChanges()) {
+ int mode = change.getMode();
+ SurfaceControl leash = change.getLeash();
+ // Reparent leash to the transition root.
+ mStartTransaction.reparent(leash, rootLeash);
+ if (TransitionUtil.isOpeningMode(mode)) {
+ openingSurfaces.add(change.getLeash());
+ // For opening surfaces, ending bounds are base bound. Apply corner radius if
+ // it's full screen.
+ Rect bounds = change.getEndAbsBounds();
+ if (displayBounds.equals(bounds)) {
+ mStartTransaction
+ .setCornerRadius(leash, windowRadius)
+ .setWindowCrop(leash, bounds.width(), bounds.height());
+ }
+ } else if (TransitionUtil.isClosingMode(mode)) {
+ closingSurfaces.add(change.getLeash());
+ // For closing surfaces, starting bounds are base bounds. Apply corner radius if
+ // it's full screen.
+ Rect bounds = change.getStartAbsBounds();
+ if (displayBounds.equals(bounds)) {
+ mStartTransaction
+ .setCornerRadius(leash, windowRadius)
+ .setWindowCrop(leash, bounds.width(), bounds.height());
+ }
+ }
+ }
+
+ // Set relative order:
+ // ---- App1 ----
+ // ---- origin ----
+ // ---- App2 ----
+ if (mIsEntry) {
+ mStartTransaction
+ .setRelativeLayer(mOriginLeash, closingSurfaces.get(0), 1)
+ .setRelativeLayer(
+ openingSurfaces.get(openingSurfaces.size() - 1), mOriginLeash, 1);
+ } else {
+ mStartTransaction
+ .setRelativeLayer(mOriginLeash, openingSurfaces.get(0), 1)
+ .setRelativeLayer(
+ closingSurfaces.get(closingSurfaces.size() - 1), mOriginLeash, 1);
+ }
+
+ // Attach origin UIComponent to origin leash.
+ mOriginTransaction = mOrigin.newTransaction();
+ mOriginTransaction
+ .attachToTransitionLeash(
+ mOrigin, mOriginLeash, displayBounds.width(), displayBounds.height())
+ .commit();
+
+ // Apply all surface changes.
+ mStartTransaction.apply();
+ return true;
+ }
+
+ private Rect getDisplayBounds(int displayId) {
+ DisplayManager dm = mContext.getSystemService(DisplayManager.class);
+ DisplayMetrics metrics = new DisplayMetrics();
+ dm.getDisplay(displayId).getMetrics(metrics);
+ return new Rect(0, 0, metrics.widthPixels, metrics.heightPixels);
+ }
+
+ private void finishAnimation(boolean finished) {
+ logD("finishAnimation: finished=" + finished);
+ if (mAnimator == null) {
+ // The transition didn't start. Ensure we apply the start transaction and report
+ // finish afterwards.
+ mStartTransaction
+ .addTransactionCommittedListener(
+ mContext.getMainExecutor(), this::finishInternal)
+ .apply();
+ return;
+ }
+ mAnimator = null;
+ // Notify client that we have ended.
+ mPlayer.onEnd(finished);
+ // Detach the origin from the transition leash and report finish after it's done.
+ mOriginTransaction
+ .detachFromTransitionLeash(
+ mOrigin, mContext.getMainExecutor(), this::finishInternal)
+ .commit();
+ }
+
+ private void finishInternal() {
+ logD("finishInternal");
+ if (mOriginLeash != null) {
+ // Release origin leash.
+ mOriginLeash.release();
+ mOriginLeash = null;
+ }
+ try {
+ mFinishCallback.onTransitionFinished(null, null);
+ } catch (RemoteException e) {
+ logE("Unable to report transition finish!", e);
+ }
+ mStartTransaction = null;
+ mOriginTransaction = null;
+ mFinishCallback = null;
+ }
+
+ private void cancel() {
+ if (mAnimator != null) {
+ mAnimator.cancel();
+ }
+ }
+
+ private static void logD(String msg) {
+ if (OriginTransitionSession.DEBUG) {
+ Log.d(TAG, msg);
+ }
+ }
+
+ private static void logE(String msg) {
+ Log.e(TAG, msg);
+ }
+
+ private static void logE(String msg, Throwable e) {
+ Log.e(TAG, msg, e);
+ }
+
+ private static UIComponent wrapSurfaces(TransitionInfo info, boolean isOpening) {
+ List<SurfaceControl> surfaces = new ArrayList<>();
+ Rect maxBounds = new Rect();
+ for (Change change : info.getChanges()) {
+ int mode = change.getMode();
+ if (TransitionUtil.isOpeningMode(mode) == isOpening) {
+ surfaces.add(change.getLeash());
+ Rect bounds = isOpening ? change.getEndAbsBounds() : change.getStartAbsBounds();
+ maxBounds.union(bounds);
+ }
+ }
+ return new SurfaceUIComponent(
+ surfaces,
+ /* alpha= */ 1.0f,
+ /* visible= */ true,
+ /* bounds= */ maxBounds,
+ /* baseBounds= */ maxBounds);
+ }
+
+ /** An interface that represents an origin transitions. */
+ public interface TransitionPlayer {
+
+ /**
+ * Called when an origin transition starts. This method exposes the raw {@link
+ * TransitionInfo} so that clients can extract more information from it.
+ */
+ default void onStart(
+ TransitionInfo transitionInfo,
+ SurfaceControl.Transaction sfTransaction,
+ UIComponent origin,
+ UIComponent.Transaction uiTransaction) {
+ // Wrap transactions.
+ Transactions transactions =
+ new Transactions()
+ .registerTransactionForClass(origin.getClass(), uiTransaction)
+ .registerTransactionForClass(
+ SurfaceUIComponent.class,
+ new SurfaceUIComponent.Transaction(sfTransaction));
+ // Wrap surfaces and start.
+ onStart(
+ transactions,
+ origin,
+ wrapSurfaces(transitionInfo, /* isOpening= */ false),
+ wrapSurfaces(transitionInfo, /* isOpening= */ true));
+ }
+
+ /**
+ * Called when an origin transition starts. This method exposes the opening and closing
+ * windows as wrapped {@link UIComponent} to provide simplified interface to clients.
+ */
+ void onStart(
+ UIComponent.Transaction transaction,
+ UIComponent origin,
+ UIComponent closingApp,
+ UIComponent openingApp);
+
+ /** Called to update the transition frame. */
+ void onProgress(float progress);
+
+ /** Called when the transition ended. */
+ void onEnd(boolean finished);
+ }
+}
diff --git a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginTransitionSession.java b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginTransitionSession.java
index 64bedd347d7a..23693b68a920 100644
--- a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginTransitionSession.java
+++ b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/OriginTransitionSession.java
@@ -24,11 +24,14 @@ import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
+import android.os.Handler;
+import android.os.Looper;
import android.os.RemoteException;
import android.util.Log;
import android.window.IRemoteTransition;
import android.window.RemoteTransition;
+import com.android.systemui.animation.OriginRemoteTransition.TransitionPlayer;
import com.android.systemui.animation.shared.IOriginTransitions;
import java.lang.annotation.Retention;
@@ -182,6 +185,7 @@ public class OriginTransitionSession {
@Nullable private final IOriginTransitions mOriginTransitions;
@Nullable private Supplier<IRemoteTransition> mEntryTransitionSupplier;
@Nullable private Supplier<IRemoteTransition> mExitTransitionSupplier;
+ private Handler mHandler = new Handler(Looper.getMainLooper());
private String mName;
@Nullable private Predicate<RemoteTransition> mIntentStarter;
@@ -259,12 +263,48 @@ public class OriginTransitionSession {
return this;
}
+ /** Add an origin entry transition to the builder. */
+ public Builder withEntryTransition(
+ UIComponent entryOrigin, TransitionPlayer entryPlayer, long entryDuration) {
+ mEntryTransitionSupplier =
+ () ->
+ new OriginRemoteTransition(
+ mContext,
+ /* isEntry= */ true,
+ entryOrigin,
+ entryPlayer,
+ entryDuration,
+ mHandler);
+ return this;
+ }
+
/** Add an exit transition to the builder. */
public Builder withExitTransition(IRemoteTransition transition) {
mExitTransitionSupplier = () -> transition;
return this;
}
+ /** Add an origin exit transition to the builder. */
+ public Builder withExitTransition(
+ UIComponent exitTarget, TransitionPlayer exitPlayer, long exitDuration) {
+ mExitTransitionSupplier =
+ () ->
+ new OriginRemoteTransition(
+ mContext,
+ /* isEntry= */ false,
+ exitTarget,
+ exitPlayer,
+ exitDuration,
+ mHandler);
+ return this;
+ }
+
+ /** Supply a handler where transition callbacks will run. */
+ public Builder withHandler(Handler handler) {
+ mHandler = handler;
+ return this;
+ }
+
/** Build an {@link OriginTransitionSession}. */
public OriginTransitionSession build() {
if (mIntentStarter == null) {
diff --git a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/SurfaceUIComponent.java b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/SurfaceUIComponent.java
new file mode 100644
index 000000000000..24387360936b
--- /dev/null
+++ b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/SurfaceUIComponent.java
@@ -0,0 +1,169 @@
+/*
+ * 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.animation;
+
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.view.SurfaceControl;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.concurrent.Executor;
+
+/** A {@link UIComponent} representing a {@link SurfaceControl}. */
+public class SurfaceUIComponent implements UIComponent {
+ private final Collection<SurfaceControl> mSurfaces;
+ private final Rect mBaseBounds;
+ private final float[] mFloat9 = new float[9];
+
+ private float mAlpha;
+ private boolean mVisible;
+ private Rect mBounds;
+
+ public SurfaceUIComponent(
+ SurfaceControl sc, float alpha, boolean visible, Rect bounds, Rect baseBounds) {
+ this(Arrays.asList(sc), alpha, visible, bounds, baseBounds);
+ }
+
+ public SurfaceUIComponent(
+ Collection<SurfaceControl> surfaces,
+ float alpha,
+ boolean visible,
+ Rect bounds,
+ Rect baseBounds) {
+ mSurfaces = surfaces;
+ mAlpha = alpha;
+ mVisible = visible;
+ mBounds = bounds;
+ mBaseBounds = baseBounds;
+ }
+
+ @Override
+ public float getAlpha() {
+ return mAlpha;
+ }
+
+ @Override
+ public boolean isVisible() {
+ return mVisible;
+ }
+
+ @Override
+ public Rect getBounds() {
+ return mBounds;
+ }
+
+ @Override
+ public Transaction newTransaction() {
+ return new Transaction(new SurfaceControl.Transaction());
+ }
+
+ @Override
+ public String toString() {
+ return "SurfaceUIComponent{mSurfaces="
+ + mSurfaces
+ + ", mAlpha="
+ + mAlpha
+ + ", mVisible="
+ + mVisible
+ + ", mBounds="
+ + mBounds
+ + ", mBaseBounds="
+ + mBaseBounds
+ + "}";
+ }
+
+ /** A {@link Transaction} wrapping a {@link SurfaceControl.Transaction}. */
+ public static class Transaction implements UIComponent.Transaction<SurfaceUIComponent> {
+ private final SurfaceControl.Transaction mTransaction;
+ private final ArrayList<Runnable> mChanges = new ArrayList<>();
+
+ public Transaction(SurfaceControl.Transaction transaction) {
+ mTransaction = transaction;
+ }
+
+ @Override
+ public Transaction setAlpha(SurfaceUIComponent ui, float alpha) {
+ mChanges.add(
+ () -> {
+ ui.mAlpha = alpha;
+ ui.mSurfaces.forEach(s -> mTransaction.setAlpha(s, alpha));
+ });
+ return this;
+ }
+
+ @Override
+ public Transaction setVisible(SurfaceUIComponent ui, boolean visible) {
+ mChanges.add(
+ () -> {
+ ui.mVisible = visible;
+ if (visible) {
+ ui.mSurfaces.forEach(s -> mTransaction.show(s));
+ } else {
+ ui.mSurfaces.forEach(s -> mTransaction.hide(s));
+ }
+ });
+ return this;
+ }
+
+ @Override
+ public Transaction setBounds(SurfaceUIComponent ui, Rect bounds) {
+ mChanges.add(
+ () -> {
+ if (ui.mBounds.equals(bounds)) {
+ return;
+ }
+ ui.mBounds = bounds;
+ Matrix matrix = new Matrix();
+ matrix.setRectToRect(
+ new RectF(ui.mBaseBounds),
+ new RectF(ui.mBounds),
+ Matrix.ScaleToFit.FILL);
+ ui.mSurfaces.forEach(s -> mTransaction.setMatrix(s, matrix, ui.mFloat9));
+ });
+ return this;
+ }
+
+ @Override
+ public Transaction attachToTransitionLeash(
+ SurfaceUIComponent ui, SurfaceControl transitionLeash, int w, int h) {
+ mChanges.add(
+ () -> ui.mSurfaces.forEach(s -> mTransaction.reparent(s, transitionLeash)));
+ return this;
+ }
+
+ @Override
+ public Transaction detachFromTransitionLeash(
+ SurfaceUIComponent ui, Executor executor, Runnable onDone) {
+ mChanges.add(
+ () -> {
+ ui.mSurfaces.forEach(s -> mTransaction.reparent(s, null));
+ mTransaction.addTransactionCommittedListener(executor, onDone::run);
+ });
+ return this;
+ }
+
+ @Override
+ public void commit() {
+ mChanges.forEach(Runnable::run);
+ mChanges.clear();
+ mTransaction.apply();
+ }
+ }
+}
diff --git a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/Transactions.java b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/Transactions.java
new file mode 100644
index 000000000000..5240d99a9217
--- /dev/null
+++ b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/Transactions.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.systemui.animation;
+
+import android.annotation.FloatRange;
+import android.graphics.Rect;
+import android.util.ArrayMap;
+import android.view.SurfaceControl;
+
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+/**
+ * A composite {@link UIComponent.Transaction} that combines multiple other transactions for each ui
+ * type.
+ */
+public class Transactions implements UIComponent.Transaction<UIComponent> {
+ private final Map<Class, UIComponent.Transaction> mTransactions = new ArrayMap<>();
+
+ /** Register a transaction object for updating a certain {@link UIComponent} type. */
+ public <T extends UIComponent> Transactions registerTransactionForClass(
+ Class<T> clazz, UIComponent.Transaction transaction) {
+ mTransactions.put(clazz, transaction);
+ return this;
+ }
+
+ private UIComponent.Transaction getTransactionFor(UIComponent ui) {
+ UIComponent.Transaction transaction = mTransactions.get(ui.getClass());
+ if (transaction == null) {
+ transaction = ui.newTransaction();
+ mTransactions.put(ui.getClass(), transaction);
+ }
+ return transaction;
+ }
+
+ @Override
+ public Transactions setAlpha(UIComponent ui, @FloatRange(from = 0.0, to = 1.0) float alpha) {
+ getTransactionFor(ui).setAlpha(ui, alpha);
+ return this;
+ }
+
+ @Override
+ public Transactions setVisible(UIComponent ui, boolean visible) {
+ getTransactionFor(ui).setVisible(ui, visible);
+ return this;
+ }
+
+ @Override
+ public Transactions setBounds(UIComponent ui, Rect bounds) {
+ getTransactionFor(ui).setBounds(ui, bounds);
+ return this;
+ }
+
+ @Override
+ public Transactions attachToTransitionLeash(
+ UIComponent ui, SurfaceControl transitionLeash, int w, int h) {
+ getTransactionFor(ui).attachToTransitionLeash(ui, transitionLeash, w, h);
+ return this;
+ }
+
+ @Override
+ public Transactions detachFromTransitionLeash(
+ UIComponent ui, Executor executor, Runnable onDone) {
+ getTransactionFor(ui).detachFromTransitionLeash(ui, executor, onDone);
+ return this;
+ }
+
+ @Override
+ public void commit() {
+ mTransactions.values().forEach(UIComponent.Transaction::commit);
+ }
+}
diff --git a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/UIComponent.java b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/UIComponent.java
new file mode 100644
index 000000000000..747e4d1eb278
--- /dev/null
+++ b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/UIComponent.java
@@ -0,0 +1,72 @@
+/*
+ * 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.animation;
+
+import android.annotation.FloatRange;
+import android.graphics.Rect;
+import android.view.SurfaceControl;
+
+import java.util.concurrent.Executor;
+
+/** An interface representing an UI component on the display. */
+public interface UIComponent {
+
+ /** Get the current alpha of this UI. */
+ float getAlpha();
+
+ /** Check if this UI is visible. */
+ boolean isVisible();
+
+ /** Get the bounds of this UI in its display. */
+ Rect getBounds();
+
+ /** Create a new {@link Transaction} that can update this UI. */
+ Transaction newTransaction();
+
+ /**
+ * A transaction class for updating {@link UIComponent}.
+ *
+ * @param <T> the subtype of {@link UIComponent} that this {@link Transaction} can handle.
+ */
+ interface Transaction<T extends UIComponent> {
+ /** Update alpha of an UI. Execution will be delayed until {@link #commit()} is called. */
+ Transaction setAlpha(T ui, @FloatRange(from = 0.0, to = 1.0) float alpha);
+
+ /**
+ * Update visibility of an UI. Execution will be delayed until {@link #commit()} is called.
+ */
+ Transaction setVisible(T ui, boolean visible);
+
+ /** Update bounds of an UI. Execution will be delayed until {@link #commit()} is called. */
+ Transaction setBounds(T ui, Rect bounds);
+
+ /**
+ * Attach a ui to the transition leash. Execution will be delayed until {@link #commit()} is
+ * called.
+ */
+ Transaction attachToTransitionLeash(T ui, SurfaceControl transitionLeash, int w, int h);
+
+ /**
+ * Detach a ui from the transition leash. Execution will be delayed until {@link #commit} is
+ * called.
+ */
+ Transaction detachFromTransitionLeash(T ui, Executor executor, Runnable onDone);
+
+ /** Commit any pending changes added to this transaction. */
+ void commit();
+ }
+}
diff --git a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/ViewUIComponent.java b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/ViewUIComponent.java
new file mode 100644
index 000000000000..313789c4ca7e
--- /dev/null
+++ b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/ViewUIComponent.java
@@ -0,0 +1,278 @@
+/*
+ * 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.animation;
+
+import android.annotation.Nullable;
+import android.graphics.Canvas;
+import android.graphics.PorterDuff;
+import android.graphics.Rect;
+import android.os.Build;
+import android.util.Log;
+import android.view.Surface;
+import android.view.SurfaceControl;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver.OnDrawListener;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ * A {@link UIComponent} wrapping a {@link View}. After being attached to the transition leash, this
+ * class will draw the content of the {@link View} directly into the leash, and the actual View will
+ * be changed to INVISIBLE in its view tree. This allows the {@link View} to transform in the
+ * full-screen size leash without being constrained by the view tree's boundary or inheriting its
+ * parent's alpha and transformation.
+ */
+public class ViewUIComponent implements UIComponent {
+ private static final String TAG = "ViewUIComponent";
+ private static final boolean DEBUG = Build.IS_USERDEBUG || Log.isLoggable(TAG, Log.DEBUG);
+ private final OnDrawListener mOnDrawListener = this::postDraw;
+ private final View mView;
+
+ @Nullable private SurfaceControl mSurfaceControl;
+ @Nullable private Surface mSurface;
+ @Nullable private Rect mViewBoundsOverride;
+ private boolean mVisibleOverride;
+ private boolean mDirty;
+
+ public ViewUIComponent(View view) {
+ mView = view;
+ }
+
+ @Override
+ public float getAlpha() {
+ return mView.getAlpha();
+ }
+
+ @Override
+ public boolean isVisible() {
+ return isAttachedToLeash() ? mVisibleOverride : mView.getVisibility() == View.VISIBLE;
+ }
+
+ @Override
+ public Rect getBounds() {
+ if (isAttachedToLeash() && mViewBoundsOverride != null) {
+ return mViewBoundsOverride;
+ }
+ return getRealBounds();
+ }
+
+ @Override
+ public Transaction newTransaction() {
+ return new Transaction();
+ }
+
+ private void attachToTransitionLeash(SurfaceControl transitionLeash, int w, int h) {
+ logD("attachToTransitionLeash");
+ // Remember current visibility.
+ mVisibleOverride = mView.getVisibility() == View.VISIBLE;
+
+ // Create the surface
+ mSurfaceControl =
+ new SurfaceControl.Builder().setName("ViewUIComponent").setBufferSize(w, h).build();
+ mSurface = new Surface(mSurfaceControl);
+ forceDraw();
+
+ // Attach surface to transition leash
+ SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ t.reparent(mSurfaceControl, transitionLeash).show(mSurfaceControl);
+
+ // Make sure view draw triggers surface draw.
+ mView.getViewTreeObserver().addOnDrawListener(mOnDrawListener);
+
+ // Make the view invisible AFTER the surface is shown.
+ t.addTransactionCommittedListener(
+ mView.getContext().getMainExecutor(),
+ () -> mView.setVisibility(View.INVISIBLE))
+ .apply();
+ }
+
+ private void detachFromTransitionLeash(Executor executor, Runnable onDone) {
+ logD("detachFromTransitionLeash");
+ Surface s = mSurface;
+ SurfaceControl sc = mSurfaceControl;
+ mSurface = null;
+ mSurfaceControl = null;
+ mView.getViewTreeObserver().removeOnDrawListener(mOnDrawListener);
+ // Restore view visibility
+ mView.setVisibility(mVisibleOverride ? View.VISIBLE : View.INVISIBLE);
+ mView.invalidate();
+ // Clean up surfaces.
+ SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ t.reparent(sc, null)
+ .addTransactionCommittedListener(
+ mView.getContext().getMainExecutor(),
+ () -> {
+ s.release();
+ sc.release();
+ executor.execute(onDone);
+ });
+ // Apply transaction AFTER the view is drawn.
+ mView.getRootSurfaceControl().applyTransactionOnDraw(t);
+ }
+
+ @Override
+ public String toString() {
+ return "ViewUIComponent{"
+ + "alpha="
+ + getAlpha()
+ + ", visible="
+ + isVisible()
+ + ", bounds="
+ + getBounds()
+ + ", attached="
+ + isAttachedToLeash()
+ + "}";
+ }
+
+ private void draw() {
+ if (!mDirty) {
+ // No need to draw. This is probably a duplicate call.
+ logD("draw: skipped - clean");
+ return;
+ }
+ mDirty = false;
+ if (!isAttachedToLeash()) {
+ // Not attached.
+ logD("draw: skipped - not attached");
+ return;
+ }
+ ViewGroup.LayoutParams params = mView.getLayoutParams();
+ if (params == null || params.width == 0 || params.height == 0) {
+ // layout pass didn't happen.
+ logD("draw: skipped - no layout");
+ return;
+ }
+ Canvas canvas = mSurface.lockHardwareCanvas();
+ // Clear the canvas first.
+ canvas.drawColor(0, PorterDuff.Mode.CLEAR);
+ if (mVisibleOverride) {
+ Rect realBounds = getRealBounds();
+ Rect renderBounds = getBounds();
+ canvas.translate(renderBounds.left, renderBounds.top);
+ canvas.scale(
+ (float) renderBounds.width() / realBounds.width(),
+ (float) renderBounds.height() / realBounds.height());
+ canvas.saveLayerAlpha(null, (int) (255 * mView.getAlpha()));
+ mView.draw(canvas);
+ canvas.restore();
+ }
+ mSurface.unlockCanvasAndPost(canvas);
+ logD("draw: done");
+ }
+
+ private void forceDraw() {
+ mDirty = true;
+ draw();
+ }
+
+ private Rect getRealBounds() {
+ Rect output = new Rect();
+ mView.getBoundsOnScreen(output);
+ return output;
+ }
+
+ private boolean isAttachedToLeash() {
+ return mSurfaceControl != null && mSurface != null;
+ }
+
+ private void logD(String msg) {
+ if (DEBUG) {
+ Log.d(TAG, msg);
+ }
+ }
+
+ private void setVisible(boolean visible) {
+ logD("setVisibility: " + visible);
+ if (isAttachedToLeash()) {
+ mVisibleOverride = visible;
+ postDraw();
+ } else {
+ mView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
+ }
+ }
+
+ private void setBounds(Rect bounds) {
+ logD("setBounds: " + bounds);
+ mViewBoundsOverride = bounds;
+ if (isAttachedToLeash()) {
+ postDraw();
+ } else {
+ Log.w(TAG, "setBounds: not attached to leash!");
+ }
+ }
+
+ private void setAlpha(float alpha) {
+ logD("setAlpha: " + alpha);
+ mView.setAlpha(alpha);
+ if (isAttachedToLeash()) {
+ postDraw();
+ }
+ }
+
+ private void postDraw() {
+ if (mDirty) {
+ return;
+ }
+ mDirty = true;
+ mView.post(this::draw);
+ }
+
+ public static class Transaction implements UIComponent.Transaction<ViewUIComponent> {
+ private final List<Runnable> mChanges = new ArrayList<>();
+
+ @Override
+ public Transaction setAlpha(ViewUIComponent ui, float alpha) {
+ mChanges.add(() -> ui.setAlpha(alpha));
+ return this;
+ }
+
+ @Override
+ public Transaction setVisible(ViewUIComponent ui, boolean visible) {
+ mChanges.add(() -> ui.setVisible(visible));
+ return this;
+ }
+
+ @Override
+ public Transaction setBounds(ViewUIComponent ui, Rect bounds) {
+ mChanges.add(() -> ui.setBounds(bounds));
+ return this;
+ }
+
+ @Override
+ public Transaction attachToTransitionLeash(
+ ViewUIComponent ui, SurfaceControl transitionLeash, int w, int h) {
+ mChanges.add(() -> ui.attachToTransitionLeash(transitionLeash, w, h));
+ return this;
+ }
+
+ @Override
+ public Transaction detachFromTransitionLeash(
+ ViewUIComponent ui, Executor executor, Runnable onDone) {
+ mChanges.add(() -> ui.detachFromTransitionLeash(executor, onDone));
+ return this;
+ }
+
+ @Override
+ public void commit() {
+ mChanges.forEach(Runnable::run);
+ mChanges.clear();
+ }
+ }
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/PlatformButtons.kt b/packages/SystemUI/compose/core/src/com/android/compose/PlatformButtons.kt
index a5f8057b524f..20efea513b3a 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/PlatformButtons.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/PlatformButtons.kt
@@ -28,11 +28,11 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.IconButtonColors
import androidx.compose.material3.IconButtonDefaults
+import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
-import com.android.compose.theme.LocalAndroidColorScheme
@Composable
fun PlatformButton(
@@ -100,12 +100,7 @@ fun PlatformIconButton(
@DrawableRes iconResource: Int,
contentDescription: String?,
) {
- IconButton(
- modifier = modifier,
- onClick = onClick,
- enabled = enabled,
- colors = colors,
- ) {
+ IconButton(modifier = modifier, onClick = onClick, enabled = enabled, colors = colors) {
Icon(
painter = painterResource(id = iconResource),
contentDescription = contentDescription,
@@ -118,7 +113,7 @@ private val ButtonPaddings = PaddingValues(horizontal = 16.dp, vertical = 8.dp)
@Composable
private fun filledButtonColors(): ButtonColors {
- val colors = LocalAndroidColorScheme.current
+ val colors = MaterialTheme.colorScheme
return ButtonDefaults.buttonColors(
containerColor = colors.primary,
contentColor = colors.onPrimary,
@@ -127,27 +122,22 @@ private fun filledButtonColors(): ButtonColors {
@Composable
private fun outlineButtonColors(): ButtonColors {
- return ButtonDefaults.outlinedButtonColors(
- contentColor = LocalAndroidColorScheme.current.onSurface,
- )
+ return ButtonDefaults.outlinedButtonColors(contentColor = MaterialTheme.colorScheme.onSurface)
}
@Composable
private fun iconButtonColors(): IconButtonColors {
return IconButtonDefaults.filledIconButtonColors(
- contentColor = LocalAndroidColorScheme.current.onSurface,
+ contentColor = MaterialTheme.colorScheme.onSurface
)
}
@Composable
private fun outlineButtonBorder(): BorderStroke {
- return BorderStroke(
- width = 1.dp,
- color = LocalAndroidColorScheme.current.primary,
- )
+ return BorderStroke(width = 1.dp, color = MaterialTheme.colorScheme.primary)
}
@Composable
private fun textButtonColors(): ButtonColors {
- return ButtonDefaults.textButtonColors(contentColor = LocalAndroidColorScheme.current.primary)
+ return ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colorScheme.primary)
}
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 f4d1242098f9..bcd333710497 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
@@ -274,7 +274,7 @@ fun CommunalHub(
if (layoutDirection == LayoutDirection.Rtl)
screenWidth - offset.x
else offset.x,
- offset.y
+ offset.y,
) - contentOffset
val index = firstIndexAtOffset(gridState, adjustedOffset)
val key =
@@ -310,6 +310,9 @@ fun CommunalHub(
it.changedToUp() || it.changedToUpIgnoreConsumed()
}
)
+
+ // Reset state once touch ends.
+ viewModel.onResetTouchState()
}
}
}
@@ -330,7 +333,7 @@ fun CommunalHub(
if (layoutDirection == LayoutDirection.Rtl)
screenWidth - offset.x
else offset.x,
- offset.y
+ offset.y,
) - it.positionInWindow() - contentOffset
}
val index = adjustedOffset?.let { firstIndexAtOffset(gridState, it) }
@@ -344,14 +347,11 @@ fun CommunalHub(
}
}
}
- },
+ }
) {
AccessibilityContainer(viewModel) {
if (!viewModel.isEditMode && isEmptyState) {
- EmptyStateCta(
- contentPadding = contentPadding,
- viewModel = viewModel,
- )
+ EmptyStateCta(contentPadding = contentPadding, viewModel = viewModel)
} else {
val slideOffsetInPx =
with(LocalDensity.current) { Dimensions.SlideOffsetY.toPx().toInt() }
@@ -364,7 +364,7 @@ fun CommunalHub(
) +
slideInVertically(
animationSpec = tween(durationMillis = 1000, easing = Emphasized),
- initialOffsetY = { -slideOffsetInPx }
+ initialOffsetY = { -slideOffsetInPx },
),
exit =
fadeOut(
@@ -372,7 +372,7 @@ fun CommunalHub(
) +
slideOutVertically(
animationSpec = tween(durationMillis = 1000, easing = Emphasized),
- targetOffsetY = { -slideOffsetInPx }
+ targetOffsetY = { -slideOffsetInPx },
),
modifier = Modifier.fillMaxSize(),
) {
@@ -389,7 +389,7 @@ fun CommunalHub(
removeEnabled = removeButtonEnabled,
offset =
gridCoordinates?.let { it.positionInWindow() + offset },
- containerToCheck = removeButtonCoordinates
+ containerToCheck = removeButtonCoordinates,
)
},
gridState = gridState,
@@ -410,7 +410,7 @@ fun CommunalHub(
enter =
fadeIn(animationSpec = tween(durationMillis = 250, easing = LinearEasing)) +
slideInVertically(
- animationSpec = tween(durationMillis = 1000, easing = Emphasized),
+ animationSpec = tween(durationMillis = 1000, easing = Emphasized)
),
exit =
fadeOut(animationSpec = tween(durationMillis = 167, easing = LinearEasing)) +
@@ -434,7 +434,7 @@ fun CommunalHub(
viewModel.setSelectedKey(null)
}
},
- removeEnabled = removeButtonEnabled
+ removeEnabled = removeButtonEnabled,
)
}
}
@@ -451,7 +451,7 @@ fun CommunalHub(
title = stringResource(id = R.string.dialog_title_to_allow_any_widget),
positiveButtonText = stringResource(id = R.string.button_text_to_open_settings),
onConfirm = viewModel::onEnableWidgetDialogConfirm,
- onCancel = viewModel::onEnableWidgetDialogCancel
+ onCancel = viewModel::onEnableWidgetDialogCancel,
)
EnableWidgetDialog(
@@ -460,7 +460,7 @@ fun CommunalHub(
title = stringResource(id = R.string.work_mode_off_title),
positiveButtonText = stringResource(id = R.string.work_mode_turn_on),
onConfirm = viewModel::onEnableWorkProfileDialogConfirm,
- onCancel = viewModel::onEnableWorkProfileDialogCancel
+ onCancel = viewModel::onEnableWorkProfileDialogCancel,
)
}
@@ -509,7 +509,7 @@ private fun DisclaimerBottomSheetContent(onButtonClicked: () -> Unit) {
imageVector = Icons.Outlined.Widgets,
contentDescription = null,
tint = colors.primary,
- modifier = Modifier.size(32.dp)
+ modifier = Modifier.size(32.dp),
)
Spacer(modifier = Modifier.height(16.dp))
Text(
@@ -527,7 +527,7 @@ private fun DisclaimerBottomSheetContent(onButtonClicked: () -> Unit) {
Modifier.padding(horizontal = 26.dp, vertical = 16.dp)
.widthIn(min = 200.dp)
.heightIn(min = 56.dp),
- onClick = { onButtonClicked() }
+ onClick = { onButtonClicked() },
) {
Text(
stringResource(R.string.communal_widgets_disclaimer_button),
@@ -540,7 +540,7 @@ private fun DisclaimerBottomSheetContent(onButtonClicked: () -> Unit) {
@Composable
private fun ObserveScrollEffect(
gridState: LazyGridState,
- communalViewModel: BaseCommunalViewModel
+ communalViewModel: BaseCommunalViewModel,
) {
LaunchedEffect(gridState) {
@@ -667,7 +667,7 @@ private fun BoxScope.CommunalHubLazyGrid(
rememberGridDragDropState(
gridState = gridState,
contentListState = contentListState,
- updateDragPositionForRemove = updateDragPositionForRemove
+ updateDragPositionForRemove = updateDragPositionForRemove,
)
gridModifier =
gridModifier
@@ -677,7 +677,7 @@ private fun BoxScope.CommunalHubLazyGrid(
LocalLayoutDirection.current,
screenWidth,
contentOffset,
- viewModel
+ viewModel,
)
// for widgets dropped from other activities
val dragAndDropTargetState =
@@ -709,11 +709,7 @@ private fun BoxScope.CommunalHubLazyGrid(
contentType = { _, item -> item.key },
span = { _, item -> GridItemSpan(item.size.span) },
) { index, item ->
- val size =
- SizeF(
- Dimensions.CardWidth.value,
- item.size.dp().value,
- )
+ val size = SizeF(Dimensions.CardWidth.value, item.size.dp().value)
val cardModifier = Modifier.requiredSize(width = size.width.dp, height = size.height.dp)
if (viewModel.isEditMode && dragDropState != null) {
val selected = item.key == selectedKey.value
@@ -765,16 +761,13 @@ private fun BoxScope.CommunalHubLazyGrid(
* The empty state displays a fullscreen call-to-action (CTA) tile when no widgets are available.
*/
@Composable
-private fun EmptyStateCta(
- contentPadding: PaddingValues,
- viewModel: BaseCommunalViewModel,
-) {
+private fun EmptyStateCta(contentPadding: PaddingValues, viewModel: BaseCommunalViewModel) {
val colors = LocalAndroidColorScheme.current
Card(
modifier = Modifier.height(hubDimensions.GridHeight).padding(contentPadding),
colors = CardDefaults.cardColors(containerColor = Color.Transparent),
border = BorderStroke(3.adjustedDp, colors.secondary),
- shape = RoundedCornerShape(size = 80.adjustedDp)
+ shape = RoundedCornerShape(size = 80.adjustedDp),
) {
Column(
modifier = Modifier.fillMaxSize().padding(horizontal = 110.adjustedDp),
@@ -788,10 +781,7 @@ private fun EmptyStateCta(
textAlign = TextAlign.Center,
color = colors.secondary,
)
- Row(
- modifier = Modifier.fillMaxWidth(),
- horizontalArrangement = Arrangement.Center,
- ) {
+ Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) {
Button(
modifier = Modifier.height(56.dp),
colors =
@@ -799,17 +789,13 @@ private fun EmptyStateCta(
containerColor = colors.primary,
contentColor = colors.onPrimary,
),
- onClick = {
- viewModel.onOpenWidgetEditor(
- shouldOpenWidgetPickerOnStart = true,
- )
- },
+ onClick = { viewModel.onOpenWidgetEditor(shouldOpenWidgetPickerOnStart = true) },
) {
Icon(
imageVector = Icons.Default.Add,
contentDescription =
stringResource(R.string.label_for_button_in_empty_state_cta),
- modifier = Modifier.size(24.dp)
+ modifier = Modifier.size(24.dp),
)
Spacer(Modifier.width(ButtonDefaults.IconSpacing))
Text(
@@ -835,7 +821,7 @@ private fun Toolbar(
setToolbarSize: (toolbarSize: IntSize) -> Unit,
setRemoveButtonCoordinates: (coordinates: LayoutCoordinates?) -> Unit,
onOpenWidgetPicker: () -> Unit,
- onEditDone: () -> Unit
+ onEditDone: () -> Unit,
) {
if (!removeEnabled) {
// Clear any existing coordinates when remove is not enabled.
@@ -844,7 +830,7 @@ private fun Toolbar(
val removeButtonAlpha: Float by
animateFloatAsState(
targetValue = if (removeEnabled) 1f else 0.5f,
- label = "RemoveButtonAlphaAnimation"
+ label = "RemoveButtonAlphaAnimation",
)
Box(
@@ -855,7 +841,7 @@ private fun Toolbar(
start = Dimensions.ToolbarPaddingHorizontal,
end = Dimensions.ToolbarPaddingHorizontal,
)
- .onSizeChanged { setToolbarSize(it) },
+ .onSizeChanged { setToolbarSize(it) }
) {
val addWidgetText = stringResource(R.string.hub_mode_add_widget_button_text)
ToolbarButton(
@@ -864,16 +850,14 @@ private fun Toolbar(
onClick = onOpenWidgetPicker,
) {
Icon(Icons.Default.Add, null)
- Text(
- text = addWidgetText,
- )
+ Text(text = addWidgetText)
}
AnimatedVisibility(
modifier = Modifier.align(Alignment.Center),
visible = removeEnabled,
enter = fadeIn(),
- exit = fadeOut()
+ exit = fadeOut(),
) {
Button(
onClick = onRemoveClicked,
@@ -887,20 +871,18 @@ private fun Toolbar(
if (removeEnabled) {
setRemoveButtonCoordinates(it)
}
- }
+ },
) {
Row(
horizontalArrangement =
Arrangement.spacedBy(
ButtonDefaults.IconSpacing,
- Alignment.CenterHorizontally
+ Alignment.CenterHorizontally,
),
- verticalAlignment = Alignment.CenterVertically
+ verticalAlignment = Alignment.CenterVertically,
) {
Icon(Icons.Default.Close, contentDescription = null)
- Text(
- text = stringResource(R.string.button_to_remove_widget),
- )
+ Text(text = stringResource(R.string.button_to_remove_widget))
}
}
}
@@ -911,9 +893,7 @@ private fun Toolbar(
onClick = onEditDone,
) {
Icon(Icons.Default.Check, contentDescription = null)
- Text(
- text = stringResource(R.string.hub_mode_editing_exit_button_text),
- )
+ Text(text = stringResource(R.string.hub_mode_editing_exit_button_text))
}
}
}
@@ -926,14 +906,14 @@ private fun ToolbarButton(
isPrimary: Boolean = true,
onClick: () -> Unit,
modifier: Modifier = Modifier,
- content: @Composable RowScope.() -> Unit
+ content: @Composable RowScope.() -> Unit,
) {
val colors = LocalAndroidColorScheme.current
AnimatedVisibility(
visible = isPrimary,
modifier = modifier,
enter = fadeIn(),
- exit = fadeOut()
+ exit = fadeOut(),
) {
Button(
onClick = onClick,
@@ -943,7 +923,7 @@ private fun ToolbarButton(
Row(
horizontalArrangement =
Arrangement.spacedBy(ButtonDefaults.IconSpacing, Alignment.CenterHorizontally),
- verticalAlignment = Alignment.CenterVertically
+ verticalAlignment = Alignment.CenterVertically,
) {
content()
}
@@ -954,21 +934,18 @@ private fun ToolbarButton(
visible = !isPrimary,
modifier = modifier,
enter = fadeIn(),
- exit = fadeOut()
+ exit = fadeOut(),
) {
OutlinedButton(
onClick = onClick,
- colors =
- ButtonDefaults.outlinedButtonColors(
- contentColor = colors.onPrimaryContainer,
- ),
+ colors = ButtonDefaults.outlinedButtonColors(contentColor = colors.onPrimaryContainer),
border = BorderStroke(width = 2.0.dp, color = colors.primary),
contentPadding = Dimensions.ButtonPadding,
) {
Row(
horizontalArrangement =
Arrangement.spacedBy(ButtonDefaults.IconSpacing, Alignment.CenterHorizontally),
- verticalAlignment = Alignment.CenterVertically
+ verticalAlignment = Alignment.CenterVertically,
) {
content()
}
@@ -1041,7 +1018,7 @@ fun HighlightedItem(modifier: Modifier = Modifier, alpha: Float = 1.0f) {
size =
Size(width = size.width + padding * 2, height = size.height + padding * 2),
cornerRadius = CornerRadius(37.adjustedDp.toPx()),
- style = Stroke(width = 3.adjustedDp.toPx())
+ style = Stroke(width = 3.adjustedDp.toPx()),
)
}
)
@@ -1061,7 +1038,7 @@ private fun CtaTileInViewModeContent(
containerColor = colors.primary,
contentColor = colors.onPrimary,
),
- shape = RoundedCornerShape(68.adjustedDp, 34.adjustedDp, 68.adjustedDp, 34.adjustedDp)
+ shape = RoundedCornerShape(68.adjustedDp, 34.adjustedDp, 68.adjustedDp, 34.adjustedDp),
) {
Column(
modifier =
@@ -1081,7 +1058,7 @@ private fun CtaTileInViewModeContent(
style = MaterialTheme.typography.titleLarge,
fontSize = nonScalableTextSize(22.dp),
lineHeight = nonScalableTextSize(28.dp),
- modifier = Modifier.verticalScroll(rememberScrollState()).weight(1F)
+ modifier = Modifier.verticalScroll(rememberScrollState()).weight(1F),
)
Spacer(modifier = Modifier.size(16.adjustedDp))
Row(
@@ -1093,15 +1070,12 @@ private fun CtaTileInViewModeContent(
LocalDensity provides
Density(
LocalDensity.current.density,
- LocalDensity.current.fontScale.coerceIn(0f, 1.25f)
+ LocalDensity.current.fontScale.coerceIn(0f, 1.25f),
)
) {
OutlinedButton(
modifier = Modifier.fillMaxHeight().weight(1F),
- colors =
- ButtonDefaults.buttonColors(
- contentColor = colors.onPrimary,
- ),
+ colors = ButtonDefaults.buttonColors(contentColor = colors.onPrimary),
border = BorderStroke(width = 1.0.dp, color = colors.primaryContainer),
onClick = viewModel::onDismissCtaTile,
contentPadding = PaddingValues(0.dp, 0.dp, 0.dp, 0.dp),
@@ -1259,7 +1233,7 @@ private fun WidgetContent(
visible = selected,
model = model,
widgetConfigurator = widgetConfigurator,
- modifier = Modifier.align(Alignment.BottomEnd)
+ modifier = Modifier.align(Alignment.BottomEnd),
)
}
}
@@ -1289,14 +1263,14 @@ fun WidgetConfigureButton(
containerColor = colors.primary,
contentColor = colors.onPrimary,
disabledContainerColor = Color.Transparent,
- disabledContentColor = Color.Transparent
+ disabledContentColor = Color.Transparent,
),
onClick = { scope.launch { widgetConfigurator.configureWidget(model.appWidgetId) } },
) {
Icon(
imageVector = Icons.Outlined.Edit,
contentDescription = stringResource(id = R.string.edit_widget),
- modifier = Modifier.padding(12.adjustedDp)
+ modifier = Modifier.padding(12.adjustedDp),
)
}
}
@@ -1323,13 +1297,13 @@ fun DisabledWidgetPlaceholder(
.background(
color = MaterialTheme.colorScheme.surfaceVariant,
shape =
- RoundedCornerShape(dimensionResource(system_app_widget_background_radius))
+ RoundedCornerShape(dimensionResource(system_app_widget_background_radius)),
)
.clickable(
enabled = !viewModel.isEditMode,
interactionSource = null,
indication = null,
- onClick = viewModel::onOpenEnableWidgetDialog
+ onClick = viewModel::onOpenEnableWidgetDialog,
),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
@@ -1360,7 +1334,7 @@ fun PendingWidgetPlaceholder(
modifier =
modifier.background(
color = MaterialTheme.colorScheme.surfaceVariant,
- shape = RoundedCornerShape(dimensionResource(system_app_widget_background_radius))
+ shape = RoundedCornerShape(dimensionResource(system_app_widget_background_radius)),
),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
@@ -1418,7 +1392,7 @@ private fun Umo(viewModel: BaseCommunalViewModel, modifier: Modifier = Modifier)
MotionEvent.ACTION_MOVE,
change.position.x,
change.position.y,
- 0
+ 0,
)
viewModel.mediaHost.hostView.dispatchTouchEvent(event)
event.recycle()
@@ -1429,12 +1403,12 @@ private fun Umo(viewModel: BaseCommunalViewModel, modifier: Modifier = Modifier)
layoutParams =
FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
- FrameLayout.LayoutParams.MATCH_PARENT
+ FrameLayout.LayoutParams.MATCH_PARENT,
)
}
viewModel.mediaHost.hostView
},
- onReset = {}
+ onReset = {},
)
}
@@ -1462,7 +1436,7 @@ fun AccessibilityContainer(viewModel: BaseCommunalViewModel, content: @Composabl
) {
viewModel.changeScene(
CommunalScenes.Blank,
- "closed by accessibility"
+ "closed by accessibility",
)
true
},
@@ -1471,7 +1445,7 @@ fun AccessibilityContainer(viewModel: BaseCommunalViewModel, content: @Composabl
) {
viewModel.onOpenWidgetEditor()
true
- }
+ },
)
}
}
@@ -1514,7 +1488,7 @@ private fun gridContentPadding(isEditMode: Boolean, toolbarSize: IntSize?): Padd
start = Dimensions.ToolbarPaddingHorizontal,
end = Dimensions.ToolbarPaddingHorizontal,
top = verticalPadding + toolbarHeight,
- bottom = verticalPadding
+ bottom = verticalPadding,
)
}
@@ -1523,7 +1497,7 @@ private fun beforeContentPadding(paddingValues: PaddingValues): ContentPaddingIn
return with(LocalDensity.current) {
ContentPaddingInPx(
start = paddingValues.calculateStartPadding(LocalLayoutDirection.current).toPx(),
- top = paddingValues.calculateTopPadding().toPx()
+ top = paddingValues.calculateTopPadding().toPx(),
)
}
}
@@ -1536,7 +1510,7 @@ private fun beforeContentPadding(paddingValues: PaddingValues): ContentPaddingIn
fun isPointerWithinEnabledRemoveButton(
removeEnabled: Boolean,
offset: Offset?,
- containerToCheck: LayoutCoordinates?
+ containerToCheck: LayoutCoordinates?,
): Boolean {
if (!removeEnabled || offset == null || containerToCheck == null) {
return false
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt
index e41a7df39c21..a88ad946d95b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalScene.kt
@@ -21,11 +21,10 @@ import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.SceneScope
-import com.android.compose.animation.scene.Swipe
-import com.android.compose.animation.scene.SwipeDirection
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.communal.shared.model.CommunalBackgroundType
+import com.android.systemui.communal.ui.viewmodel.CommunalUserActionsViewModel
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.communal.util.CommunalColors
import com.android.systemui.dagger.SysUISingleton
@@ -33,38 +32,32 @@ import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.composable.Scene
import javax.inject.Inject
-import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
/** The communal scene shows glanceable hub when the device is locked and docked. */
@SysUISingleton
class CommunalScene
@Inject
constructor(
- private val viewModel: CommunalViewModel,
+ private val contentViewModel: CommunalViewModel,
+ actionsViewModelFactory: CommunalUserActionsViewModel.Factory,
private val communalColors: CommunalColors,
private val communalContent: CommunalContent,
) : ExclusiveActivatable(), Scene {
override val key = Scenes.Communal
- override val userActions: Flow<Map<UserAction, UserActionResult>> =
- MutableStateFlow(
- mapOf(
- Swipe(SwipeDirection.End) to Scenes.Lockscreen,
- )
- )
- .asStateFlow()
+ private val actionsViewModel: CommunalUserActionsViewModel = actionsViewModelFactory.create()
+
+ override val userActions: Flow<Map<UserAction, UserActionResult>> = actionsViewModel.actions
override suspend fun onActivated(): Nothing {
- awaitCancellation()
+ actionsViewModel.activate()
}
@Composable
override fun SceneScope.Content(modifier: Modifier) {
val backgroundType by
- viewModel.communalBackground.collectAsStateWithLifecycle(
+ contentViewModel.communalBackground.collectAsStateWithLifecycle(
initialValue = CommunalBackgroundType.ANIMATED
)
@@ -72,7 +65,7 @@ constructor(
backgroundType = backgroundType,
colors = communalColors,
content = communalContent,
- viewModel = viewModel,
+ viewModel = contentViewModel,
modifier = modifier.horizontalNestedScrollToScene(),
)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/dialog/ui/composable/AlertDialogContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/dialog/ui/composable/AlertDialogContent.kt
index 0b9669410b8e..69ca0a5f476c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/dialog/ui/composable/AlertDialogContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/dialog/ui/composable/AlertDialogContent.kt
@@ -38,7 +38,6 @@ import androidx.compose.ui.layout.Placeable
import androidx.compose.ui.layout.layoutId
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
-import com.android.compose.theme.LocalAndroidColorScheme
import kotlin.math.roundToInt
/**
@@ -69,7 +68,7 @@ fun AlertDialogContent(
Modifier.defaultMinSize(minWidth = defaultSize, minHeight = defaultSize),
propagateMinConstraints = true,
) {
- val iconColor = LocalAndroidColorScheme.current.primary
+ val iconColor = MaterialTheme.colorScheme.primary
CompositionLocalProvider(LocalContentColor provides iconColor) { icon() }
}
@@ -77,7 +76,7 @@ fun AlertDialogContent(
}
// Title.
- val titleColor = LocalAndroidColorScheme.current.onSurface
+ val titleColor = MaterialTheme.colorScheme.onSurface
CompositionLocalProvider(LocalContentColor provides titleColor) {
ProvideTextStyle(
MaterialTheme.typography.headlineSmall.copy(textAlign = TextAlign.Center)
@@ -88,7 +87,7 @@ fun AlertDialogContent(
Spacer(Modifier.height(16.dp))
// Content.
- val contentColor = LocalAndroidColorScheme.current.onSurfaceVariant
+ val contentColor = MaterialTheme.colorScheme.onSurfaceVariant
Box {
CompositionLocalProvider(LocalContentColor provides contentColor) {
ProvideTextStyle(
@@ -169,7 +168,7 @@ private fun AlertDialogButtons(
negative.width -
positive.width -
horizontalSpacing.roundToInt(),
- 0
+ 0,
)
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
index 4162891c0e0b..6f1349f20e4d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
@@ -18,7 +18,6 @@
package com.android.systemui.shade.ui.composable
-import android.view.HapticFeedbackConstants
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
@@ -40,20 +39,17 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalLayoutDirection
-import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.unit.dp
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.LowestZIndexContentPicker
import com.android.compose.animation.scene.SceneScope
import com.android.compose.windowsizeclass.LocalWindowSizeClass
-import com.android.systemui.scene.shared.model.Scenes
/** Renders a lightweight shade UI container, as an overlay. */
@Composable
@@ -62,13 +58,6 @@ fun SceneScope.OverlayShade(
modifier: Modifier = Modifier,
content: @Composable () -> Unit,
) {
- val view = LocalView.current
- LaunchedEffect(Unit) {
- if (layoutState.currentTransition?.fromContent == Scenes.Gone) {
- view.performHapticFeedback(HapticFeedbackConstants.GESTURE_START)
- }
- }
-
Box(modifier) {
Scrim(onClicked = onScrimClicked)
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 db0fe3e3f79d..ef415b151200 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
@@ -16,7 +16,6 @@
package com.android.systemui.shade.ui.composable
-import android.view.HapticFeedbackConstants
import android.view.ViewGroup
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.animateFloatAsState
@@ -60,7 +59,6 @@ import androidx.compose.ui.layout.layoutId
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLifecycleOwner
-import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.unit.dp
@@ -226,12 +224,6 @@ private fun SceneScope.ShadeScene(
shadeSession: SaveableSession,
usingCollapsedLandscapeMedia: Boolean,
) {
- val view = LocalView.current
- LaunchedEffect(Unit) {
- if (layoutState.currentTransition?.fromContent == Scenes.Gone) {
- view.performHapticFeedback(HapticFeedbackConstants.GESTURE_START)
- }
- }
val shadeMode by viewModel.shadeMode.collectAsStateWithLifecycle()
when (shadeMode) {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
index 9891025ad7d3..367faed7b7f7 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
@@ -188,38 +188,47 @@ internal class DraggableHandlerImpl(
return createSwipeAnimation(layoutImpl, result, isUpOrLeft, orientation)
}
- private fun computeSwipes(startedPosition: Offset?, pointersDown: Int): Swipes {
- val fromSource =
- startedPosition?.let { position ->
- layoutImpl.swipeSourceDetector.source(
- layoutImpl.lastSize,
- position.round(),
- layoutImpl.density,
- orientation,
- )
- }
+ private fun resolveSwipeSource(startedPosition: Offset?): SwipeSource.Resolved? {
+ if (startedPosition == null) return null
+ return layoutImpl.swipeSourceDetector.source(
+ layoutSize = layoutImpl.lastSize,
+ position = startedPosition.round(),
+ density = layoutImpl.density,
+ orientation = orientation,
+ )
+ }
- val upOrLeft =
- Swipe.Resolved(
- direction =
- when (orientation) {
- Orientation.Horizontal -> SwipeDirection.Resolved.Left
- Orientation.Vertical -> SwipeDirection.Resolved.Up
- },
- pointerCount = pointersDown,
- fromSource = fromSource,
- )
+ private fun resolveSwipe(
+ pointersDown: Int,
+ fromSource: SwipeSource.Resolved?,
+ isUpOrLeft: Boolean,
+ ): Swipe.Resolved {
+ return Swipe.Resolved(
+ direction =
+ when (orientation) {
+ Orientation.Horizontal ->
+ if (isUpOrLeft) {
+ SwipeDirection.Resolved.Left
+ } else {
+ SwipeDirection.Resolved.Right
+ }
- val downOrRight =
- Swipe.Resolved(
- direction =
- when (orientation) {
- Orientation.Horizontal -> SwipeDirection.Resolved.Right
- Orientation.Vertical -> SwipeDirection.Resolved.Down
- },
- pointerCount = pointersDown,
- fromSource = fromSource,
- )
+ Orientation.Vertical ->
+ if (isUpOrLeft) {
+ SwipeDirection.Resolved.Up
+ } else {
+ SwipeDirection.Resolved.Down
+ }
+ },
+ pointerCount = pointersDown,
+ fromSource = fromSource,
+ )
+ }
+
+ private fun computeSwipes(startedPosition: Offset?, pointersDown: Int): Swipes {
+ val fromSource = resolveSwipeSource(startedPosition)
+ val upOrLeft = resolveSwipe(pointersDown, fromSource, isUpOrLeft = true)
+ val downOrRight = resolveSwipe(pointersDown, fromSource, isUpOrLeft = false)
return if (fromSource == null) {
Swipes(
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
index 4bb01ec1e1df..fbed7cd48ba2 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
@@ -25,7 +25,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.os.SystemClock;
@@ -168,8 +168,8 @@ public class KeyguardAbsKeyInputViewControllerTest extends SysuiTestCase {
verify(mAbsKeyInputView).setKeyDownListener(onKeyDownListenerArgumentCaptor.capture());
onKeyDownListenerArgumentCaptor.getValue().onKeyDown(
KeyEvent.KEYCODE_UNKNOWN, mock(KeyEvent.class));
- verifyZeroInteractions(mKeyguardSecurityCallback);
- verifyZeroInteractions(mKeyguardMessageAreaController);
+ verifyNoMoreInteractions(mKeyguardSecurityCallback);
+ verifyNoMoreInteractions(mKeyguardMessageAreaController);
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
index c163c6fc0a30..0490a26019e1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
@@ -350,7 +350,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() {
testScope.runTest {
underTest.performDotFeedback(null)
- assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.DRAG_INDICATOR)
+ assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.DRAG_INDICATOR_DISCRETE)
assertThat(msdlPlayer.latestPropertiesPlayed).isNull()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt
index e25c1a71a5a6..d5020a580d00 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt
@@ -109,7 +109,9 @@ class CommunalSceneTransitionInteractorTest : SysuiTestCase() {
kosmos.fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true)
underTest.start()
kosmos.communalSceneRepository.setTransitionState(sceneTransitions)
- testScope.launch { keyguardTransitionRepository.emitInitialStepsFromOff(LOCKSCREEN) }
+ testScope.launch {
+ keyguardTransitionRepository.emitInitialStepsFromOff(LOCKSCREEN, testSetup = true)
+ }
}
/** Transition from blank to glanceable hub. This is the default case. */
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModelTest.kt
new file mode 100644
index 000000000000..58b59ffd8894
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModelTest.kt
@@ -0,0 +1,223 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.communal.ui.viewmodel
+
+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.compose.animation.scene.Swipe
+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.coroutines.collectLastValue
+import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
+import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.model.Overlays
+import com.android.systemui.scene.shared.model.SceneFamilies
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade
+import com.android.systemui.shade.data.repository.fakeShadeRepository
+import com.android.systemui.shade.shared.flag.DualShade
+import com.android.systemui.shade.shared.model.ShadeMode
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@EnableSceneContainer
+class CommunalUserActionsViewModelTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
+ private lateinit var underTest: CommunalUserActionsViewModel
+
+ @Before
+ fun setUp() {
+ underTest = kosmos.communalUserActionsViewModel
+ underTest.activateIn(testScope)
+ }
+
+ @Test
+ @DisableFlags(DualShade.FLAG_NAME)
+ fun actions_singleShade() =
+ testScope.runTest {
+ val actions by collectLastValue(underTest.actions)
+
+ setUpState(
+ isShadeTouchable = true,
+ isDeviceUnlocked = false,
+ shadeMode = ShadeMode.Single,
+ )
+ assertThat(actions).isNotEmpty()
+ assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home))
+ assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Bouncer))
+ assertThat(actions?.get(Swipe.Down)).isEqualTo(UserActionResult(Scenes.Shade))
+
+ 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.End)).isEqualTo(UserActionResult(SceneFamilies.Home))
+ assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Gone))
+ assertThat(actions?.get(Swipe.Down)).isEqualTo(UserActionResult(Scenes.Shade))
+ }
+
+ @Test
+ @DisableFlags(DualShade.FLAG_NAME)
+ fun actions_splitShade() =
+ testScope.runTest {
+ val actions by collectLastValue(underTest.actions)
+
+ setUpState(
+ isShadeTouchable = true,
+ isDeviceUnlocked = false,
+ shadeMode = ShadeMode.Split,
+ )
+ assertThat(actions).isNotEmpty()
+ assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home))
+ assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Bouncer))
+ assertThat(actions?.get(Swipe.Down))
+ .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade))
+
+ 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.End)).isEqualTo(UserActionResult(SceneFamilies.Home))
+ assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Gone))
+ assertThat(actions?.get(Swipe.Down))
+ .isEqualTo(UserActionResult(Scenes.Shade, ToSplitShade))
+ }
+
+ @Test
+ @EnableFlags(DualShade.FLAG_NAME)
+ fun actions_dualShade() =
+ testScope.runTest {
+ val actions by collectLastValue(underTest.actions)
+
+ setUpState(
+ isShadeTouchable = true,
+ isDeviceUnlocked = false,
+ shadeMode = ShadeMode.Dual,
+ )
+ assertThat(actions).isNotEmpty()
+ assertThat(actions?.get(Swipe.End)).isEqualTo(UserActionResult(SceneFamilies.Home))
+ assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Bouncer))
+ assertThat(actions?.get(Swipe.Down))
+ .isEqualTo(UserActionResult(Overlays.NotificationsShade))
+
+ 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.End)).isEqualTo(UserActionResult(SceneFamilies.Home))
+ assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Gone))
+ assertThat(actions?.get(Swipe.Down))
+ .isEqualTo(UserActionResult(Overlays.NotificationsShade))
+ }
+
+ private fun TestScope.setUpState(
+ isShadeTouchable: Boolean,
+ isDeviceUnlocked: Boolean,
+ shadeMode: ShadeMode,
+ ) {
+ if (isShadeTouchable) {
+ kosmos.powerInteractor.setAwakeForTest()
+ } else {
+ kosmos.powerInteractor.setAsleepForTest()
+ }
+
+ if (isDeviceUnlocked) {
+ unlockDevice()
+ } else {
+ lockDevice()
+ }
+
+ if (shadeMode == ShadeMode.Dual) {
+ assertThat(DualShade.isEnabled).isTrue()
+ } else {
+ assertThat(DualShade.isEnabled).isFalse()
+ kosmos.fakeShadeRepository.setShadeLayoutWide(shadeMode == ShadeMode.Split)
+ }
+ runCurrent()
+ }
+
+ private fun TestScope.lockDevice() {
+ val deviceUnlockStatus by
+ collectLastValue(kosmos.deviceUnlockedInteractor.deviceUnlockStatus)
+
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
+ kosmos.sceneInteractor.changeScene(Scenes.Lockscreen, "reason")
+ runCurrent()
+ }
+
+ private fun TestScope.unlockDevice() {
+ val deviceUnlockStatus by
+ collectLastValue(kosmos.deviceUnlockedInteractor.deviceUnlockStatus)
+
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
+ assertThat(deviceUnlockStatus?.isUnlocked).isTrue()
+ kosmos.sceneInteractor.changeScene(Scenes.Gone, "reason")
+ runCurrent()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/start/ControlsStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/start/ControlsStartableTest.kt
index 9e8914a1119f..0d8312e7f264 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/start/ControlsStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/controls/start/ControlsStartableTest.kt
@@ -69,7 +69,7 @@ import org.mockito.Mockito.doAnswer
import org.mockito.Mockito.doReturn
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@@ -109,7 +109,7 @@ class ControlsStartableTest : SysuiTestCase() {
onBootCompleted()
}
- verifyZeroInteractions(controlsController, controlsListingController, userTracker)
+ verifyNoMoreInteractions(controlsController, controlsListingController, userTracker)
}
@Test
@@ -119,7 +119,7 @@ class ControlsStartableTest : SysuiTestCase() {
fakeExecutor.advanceClockToLast()
fakeExecutor.runAllReady()
- verifyZeroInteractions(controlsController, controlsListingController, userTracker)
+ verifyNoMoreInteractions(controlsController, controlsListingController, userTracker)
}
@Test
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 ab33269ec954..d7fe263df581 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
@@ -16,10 +16,10 @@
package com.android.systemui.education.domain.ui.view
+import android.app.Dialog
import android.app.Notification
import android.app.NotificationManager
import android.content.applicationContext
-import android.widget.Toast
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -34,11 +34,13 @@ import com.android.systemui.education.ui.viewmodel.ContextualEduViewModel
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
+import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -51,6 +53,7 @@ import org.mockito.Mock
import org.mockito.junit.MockitoJUnit
import org.mockito.kotlin.any
import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -63,10 +66,12 @@ class ContextualEduUiCoordinatorTest : SysuiTestCase() {
private val minDurationForNextEdu =
KeyboardTouchpadEduInteractor.minIntervalBetweenEdu + 1.seconds
private lateinit var underTest: ContextualEduUiCoordinator
- @Mock private lateinit var toast: Toast
+ @Mock private lateinit var dialog: Dialog
@Mock private lateinit var notificationManager: NotificationManager
+ @Mock private lateinit var accessibilityManagerWrapper: AccessibilityManagerWrapper
@get:Rule val mockitoRule = MockitoJUnit.rule()
private var toastContent = ""
+ private val timeoutMillis = 3500L
@Before
fun setUp() {
@@ -75,30 +80,35 @@ class ContextualEduUiCoordinatorTest : SysuiTestCase() {
interactor.updateTouchpadFirstConnectionTime()
}
+ whenever(accessibilityManagerWrapper.getRecommendedTimeoutMillis(any(), any()))
+ .thenReturn(timeoutMillis.toInt())
+
val viewModel =
ContextualEduViewModel(
kosmos.applicationContext.resources,
- kosmos.keyboardTouchpadEduInteractor
+ kosmos.keyboardTouchpadEduInteractor,
+ accessibilityManagerWrapper,
)
+
underTest =
ContextualEduUiCoordinator(
kosmos.applicationCoroutineScope,
viewModel,
kosmos.applicationContext,
notificationManager
- ) { content ->
- toastContent = content
- toast
+ ) { model ->
+ toastContent = model.message
+ dialog
}
underTest.start()
kosmos.keyboardTouchpadEduInteractor.start()
}
@Test
- fun showToastOnNewEdu() =
+ fun showDialogOnNewEdu() =
testScope.runTest {
triggerEducation(BACK)
- verify(toast).show()
+ verify(dialog).show()
}
@Test
@@ -111,6 +121,14 @@ class ContextualEduUiCoordinatorTest : SysuiTestCase() {
}
@Test
+ fun dismissDialogAfterTimeout() =
+ testScope.runTest {
+ triggerEducation(BACK)
+ advanceTimeBy(timeoutMillis + 1)
+ verify(dialog).dismiss()
+ }
+
+ @Test
fun verifyBackEduToastContent() =
testScope.runTest {
triggerEducation(BACK)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderStateTrackerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/slider/SliderStateTrackerTest.kt
index a09d34579e2f..2e9d993c99a9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderStateTrackerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/slider/SliderStateTrackerTest.kt
@@ -32,7 +32,6 @@ import org.mockito.Mock
import org.mockito.Mockito.anyFloat
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
-import org.mockito.Mockito.verifyZeroInteractions
import org.mockito.MockitoAnnotations
@SmallTest
@@ -84,7 +83,7 @@ class SliderStateTrackerTest : SysuiTestCase() {
// THEN The state is idle and the listener is not called to play haptics
assertThat(mSliderStateTracker.currentState).isEqualTo(SliderState.IDLE)
- verifyZeroInteractions(sliderStateListener)
+ verifyNoMoreInteractions(sliderStateListener)
}
@Test
@@ -97,7 +96,7 @@ class SliderStateTrackerTest : SysuiTestCase() {
// THEN the tracker moves to the wait state and the timer job begins
assertThat(mSliderStateTracker.currentState).isEqualTo(SliderState.WAIT)
- verifyZeroInteractions(sliderStateListener)
+ verifyNoMoreInteractions(sliderStateListener)
assertThat(mSliderStateTracker.isWaiting).isTrue()
}
@@ -591,7 +590,7 @@ class SliderStateTrackerTest : SysuiTestCase() {
// THEN the tracker moves back to IDLE and there are no haptics
assertThat(mSliderStateTracker.currentState).isEqualTo(SliderState.IDLE)
- verifyZeroInteractions(sliderStateListener)
+ verifyNoMoreInteractions(sliderStateListener)
}
@Test
@@ -608,7 +607,7 @@ class SliderStateTrackerTest : SysuiTestCase() {
// haptics
assertThat(mSliderStateTracker.currentState).isEqualTo(SliderState.WAIT)
assertThat(mSliderStateTracker.isWaiting).isTrue()
- verifyZeroInteractions(sliderStateListener)
+ verifyNoMoreInteractions(sliderStateListener)
}
@Test
@@ -644,7 +643,7 @@ class SliderStateTrackerTest : SysuiTestCase() {
// THEN the tracker moves to IDLE and no haptics are played
assertThat(mSliderStateTracker.currentState).isEqualTo(SliderState.IDLE)
- verifyZeroInteractions(sliderStateListener)
+ verifyNoMoreInteractions(sliderStateListener)
}
@Test
@@ -660,7 +659,7 @@ class SliderStateTrackerTest : SysuiTestCase() {
// THEN the tracker moves to WAIT and the wait job starts.
assertThat(mSliderStateTracker.currentState).isEqualTo(SliderState.WAIT)
assertThat(mSliderStateTracker.isWaiting).isTrue()
- verifyZeroInteractions(sliderStateListener)
+ verifyNoMoreInteractions(sliderStateListener)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinatorTest.kt
index 9a721fa26df3..727481e0637d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinatorTest.kt
@@ -38,7 +38,7 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.verifyNoMoreInteractions
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -75,7 +75,7 @@ class StickyKeysIndicatorCoordinatorTest : SysuiTestCase() {
@Test
fun dialogIsShownWhenStickyKeysAreEmitted() {
testScope.run {
- verifyZeroInteractions(dialog)
+ verifyNoMoreInteractions(dialog)
stickyKeysRepository.setStickyKeys(linkedMapOf(SHIFT to Locked(true)))
runCurrent()
@@ -87,7 +87,7 @@ class StickyKeysIndicatorCoordinatorTest : SysuiTestCase() {
@Test
fun dialogDisappearsWhenStickyKeysAreEmpty() {
testScope.run {
- verifyZeroInteractions(dialog)
+ verifyNoMoreInteractions(dialog)
stickyKeysRepository.setStickyKeys(linkedMapOf(SHIFT to Locked(true)))
runCurrent()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt
index 3ccb989dc65a..26ce67da10b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt
@@ -52,7 +52,7 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.verifyNoMoreInteractions
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -98,7 +98,7 @@ class StickyKeysIndicatorViewModelTest : SysuiTestCase() {
keyboardRepository.setIsAnyKeyboardConnected(true)
runCurrent()
- verifyZeroInteractions(inputManager)
+ verifyNoMoreInteractions(inputManager)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
index 23fd997c02e1..6b49d3a095d8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
@@ -32,7 +32,6 @@ import org.mockito.Mock
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
-import org.mockito.Mockito.verifyZeroInteractions
import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@@ -72,7 +71,7 @@ class ResourceTrimmerTest : SysuiTestCase() {
testScope.runTest {
powerInteractor.setAsleepForTest()
testScope.runCurrent()
- verifyZeroInteractions(globalWindowManager)
+ verifyNoMoreInteractions(globalWindowManager)
}
@Test
@@ -82,7 +81,7 @@ class ResourceTrimmerTest : SysuiTestCase() {
keyguardRepository.setDozeAmount(1f)
powerInteractor.setAsleepForTest()
testScope.runCurrent()
- verifyZeroInteractions(globalWindowManager)
+ verifyNoMoreInteractions(globalWindowManager)
}
@Test
@@ -92,7 +91,7 @@ class ResourceTrimmerTest : SysuiTestCase() {
keyguardRepository.setDozeAmount(0f)
powerInteractor.setAsleepForTest()
testScope.runCurrent()
- verifyZeroInteractions(globalWindowManager)
+ verifyNoMoreInteractions(globalWindowManager)
}
@Test
@@ -103,7 +102,7 @@ class ResourceTrimmerTest : SysuiTestCase() {
powerInteractor.setAsleepForTest()
testScope.runCurrent()
- verifyZeroInteractions(globalWindowManager)
+ verifyNoMoreInteractions(globalWindowManager)
generateSequence(0f) { it + 0.1f }
.takeWhile { it < 0.8f }
@@ -111,7 +110,7 @@ class ResourceTrimmerTest : SysuiTestCase() {
keyguardRepository.setDozeAmount(it)
testScope.runCurrent()
}
- verifyZeroInteractions(globalWindowManager)
+ verifyNoMoreInteractions(globalWindowManager)
generateSequence(0.8f) { it - 0.1f }
.takeWhile { it >= 0f }
@@ -122,7 +121,7 @@ class ResourceTrimmerTest : SysuiTestCase() {
keyguardRepository.setDozeAmount(0f)
testScope.runCurrent()
- verifyZeroInteractions(globalWindowManager)
+ verifyNoMoreInteractions(globalWindowManager)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt
index 5fa194d95e28..42b3463c052e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt
@@ -48,7 +48,7 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@@ -122,7 +122,7 @@ class MuteQuickAffordanceCoreStartableTest : SysuiTestCase() {
observerCaptor.value.onChanged(AudioManager.RINGER_MODE_SILENT)
//then
- verifyZeroInteractions(userFileManager)
+ verifyNoMoreInteractions(userFileManager)
coroutineContext.cancelChildren()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
index ba689179c33d..d97909a1347e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
@@ -19,17 +19,22 @@ package com.android.systemui.keyguard.domain.interactor
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
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.bouncer.data.repository.fakeKeyguardBouncerRepository
import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.shared.model.DismissAction
import com.android.systemui.keyguard.shared.model.KeyguardDone
+import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
import com.android.systemui.power.data.repository.fakePowerRepository
import com.android.systemui.power.domain.interactor.powerInteractor
@@ -44,6 +49,7 @@ import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -76,12 +82,12 @@ class KeyguardDismissActionInteractorTest : SysuiTestCase() {
transitionInteractor = kosmos.keyguardTransitionInteractor,
dismissInteractor = dismissInteractor,
applicationScope = testScope.backgroundScope,
- sceneInteractor = { kosmos.sceneInteractor },
deviceUnlockedInteractor = { kosmos.deviceUnlockedInteractor },
powerInteractor = kosmos.powerInteractor,
alternateBouncerInteractor = kosmos.alternateBouncerInteractor,
shadeInteractor = { kosmos.shadeInteractor },
keyguardInteractor = { kosmos.keyguardInteractor },
+ sceneInteractor = { kosmos.sceneInteractor },
)
}
@@ -180,7 +186,11 @@ class KeyguardDismissActionInteractorTest : SysuiTestCase() {
)
assertThat(executeDismissAction).isNull()
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
kosmos.setSceneTransition(Idle(Scenes.Gone))
+ kosmos.sceneInteractor.changeScene(Scenes.Gone, "")
assertThat(executeDismissAction).isNotNull()
}
@@ -303,4 +313,78 @@ class KeyguardDismissActionInteractorTest : SysuiTestCase() {
underTest.setKeyguardDone(KeyguardDone.IMMEDIATE)
assertThat(keyguardDoneTiming).isEqualTo(KeyguardDone.IMMEDIATE)
}
+
+ @Test
+ @EnableSceneContainer
+ fun dismissAction_executesBeforeItsReset_sceneContainerOn_swipeAuth_fromQsScene() =
+ testScope.runTest {
+ val canSwipeToEnter by collectLastValue(kosmos.deviceEntryInteractor.canSwipeToEnter)
+ val currentScene by collectLastValue(kosmos.sceneInteractor.currentScene)
+ val transitionState =
+ MutableStateFlow<ObservableTransitionState>(
+ ObservableTransitionState.Idle(currentScene!!)
+ )
+ kosmos.sceneInteractor.setTransitionState(transitionState)
+ val executeDismissAction by collectLastValue(underTest.executeDismissAction)
+ val resetDismissAction by collectLastValue(underTest.resetDismissAction)
+ assertThat(executeDismissAction).isNull()
+ assertThat(resetDismissAction).isNull()
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.None
+ )
+ kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
+ assertThat(canSwipeToEnter).isTrue()
+ kosmos.sceneInteractor.changeScene(Scenes.QuickSettings, "")
+ transitionState.value = ObservableTransitionState.Idle(Scenes.QuickSettings)
+ assertThat(currentScene).isEqualTo(Scenes.QuickSettings)
+
+ assertThat(executeDismissAction).isNull()
+ assertThat(resetDismissAction).isNull()
+
+ val dismissAction =
+ DismissAction.RunImmediately(
+ onDismissAction = { KeyguardDone.LATER },
+ onCancelAction = {},
+ message = "message",
+ willAnimateOnLockscreen = true,
+ )
+ underTest.setDismissAction(dismissAction)
+ // Should still be null because the transition to Gone has not yet happened.
+ assertThat(executeDismissAction).isNull()
+ assertThat(resetDismissAction).isNull()
+
+ transitionState.value =
+ ObservableTransitionState.Transition.ChangeScene(
+ fromScene = Scenes.QuickSettings,
+ toScene = Scenes.Gone,
+ currentScene = flowOf(Scenes.QuickSettings),
+ currentOverlays = emptySet(),
+ progress = flowOf(0.5f),
+ isInitiatedByUserInput = true,
+ isUserInputOngoing = flowOf(false),
+ previewProgress = flowOf(0f),
+ isInPreviewStage = flowOf(false),
+ )
+ runCurrent()
+ assertThat(executeDismissAction).isNull()
+ assertThat(resetDismissAction).isNull()
+
+ transitionState.value =
+ ObservableTransitionState.Transition.ChangeScene(
+ fromScene = Scenes.QuickSettings,
+ toScene = Scenes.Gone,
+ currentScene = flowOf(Scenes.Gone),
+ currentOverlays = emptySet(),
+ progress = flowOf(1f),
+ isInitiatedByUserInput = true,
+ isUserInputOngoing = flowOf(false),
+ previewProgress = flowOf(0f),
+ isInPreviewStage = flowOf(false),
+ )
+ kosmos.sceneInteractor.changeScene(Scenes.Gone, "")
+ assertThat(currentScene).isEqualTo(Scenes.Gone)
+ runCurrent()
+ assertThat(executeDismissAction).isNotNull()
+ assertThat(resetDismissAction).isNull()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
index e3bdcd707823..eef4c3de4b19 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
@@ -72,7 +72,7 @@ import org.mockito.ArgumentMatchers.eq
import org.mockito.ArgumentMatchers.same
import org.mockito.Mock
import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.MockitoAnnotations
import platform.test.runner.parameterized.Parameter
import platform.test.runner.parameterized.ParameterizedAndroidJunit4
@@ -388,7 +388,7 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() {
)
}
} else {
- verifyZeroInteractions(activityStarter)
+ verifyNoMoreInteractions(activityStarter)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt
index 591ce1aee09b..1184a76d54ff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt
@@ -72,7 +72,7 @@ import org.mockito.ArgumentMatchers.eq
import org.mockito.ArgumentMatchers.same
import org.mockito.Mock
import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.MockitoAnnotations
import platform.test.runner.parameterized.Parameter
import platform.test.runner.parameterized.ParameterizedAndroidJunit4
@@ -388,7 +388,7 @@ class KeyguardQuickAffordanceInteractorSceneContainerTest : SysuiTestCase() {
)
}
} else {
- verifyZeroInteractions(activityStarter)
+ verifyNoMoreInteractions(activityStarter)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModelTest.kt
index 129752e4f106..aab46d8cb73a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModelTest.kt
@@ -22,6 +22,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.testKosmos
import org.junit.Before
import org.junit.Test
@@ -44,6 +45,7 @@ class KeyguardBlueprintViewModelTest : SysuiTestCase() {
KeyguardBlueprintViewModel(
handler = kosmos.fakeExecutorHandler,
keyguardBlueprintInteractor = keyguardBlueprintInteractor,
+ keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index 1929cd172750..e1845a17a767 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -84,7 +84,7 @@ import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mock
import org.mockito.Mockito
-import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.MockitoAnnotations
import platform.test.runner.parameterized.ParameterizedAndroidJunit4
import platform.test.runner.parameterized.Parameters
@@ -735,7 +735,7 @@ class KeyguardBottomAreaViewModelTest(flags: FlagsParameterization) : SysuiTestC
if (testConfig.intent != null) {
assertThat(Mockito.mockingDetails(activityStarter).invocations).hasSize(1)
} else {
- verifyZeroInteractions(activityStarter)
+ verifyNoMoreInteractions(activityStarter)
}
} else {
assertThat(viewModel.isVisible).isFalse()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
index 720f2e197aea..5b216620ec2b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
@@ -862,7 +862,7 @@ class KeyguardQuickAffordancesCombinedViewModelTest : SysuiTestCase() {
if (testConfig.intent != null) {
Truth.assertThat(Mockito.mockingDetails(activityStarter).invocations).hasSize(1)
} else {
- Mockito.verifyZeroInteractions(activityStarter)
+ Mockito.verifyNoMoreInteractions(activityStarter)
}
} else {
Truth.assertThat(viewModel.isVisible).isFalse()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
index e6ea64f8ee71..d0da2e9671c0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
@@ -89,9 +89,12 @@ class LockscreenToOccludedTransitionViewModelTest(flags: FlagsParameterization)
}
@Test
- fun lockscreenFadeOut() =
+ fun lockscreenFadeOut_shadeNotExpanded() =
testScope.runTest {
val values by collectValues(underTest.lockscreenAlpha)
+ shadeExpanded(false)
+ runCurrent()
+
repository.sendTransitionSteps(
steps =
listOf(
@@ -104,10 +107,34 @@ class LockscreenToOccludedTransitionViewModelTest(flags: FlagsParameterization)
),
testScope = testScope,
)
- // Only 5 values should be present, since the dream overlay runs for a small fraction
- // of the overall animation time
assertThat(values.size).isEqualTo(5)
- values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
+ assertThat(values[0]).isEqualTo(1f)
+ assertThat(values[1]).isEqualTo(1f)
+ assertThat(values[2]).isIn(Range.open(0f, 1f))
+ assertThat(values[3]).isIn(Range.open(0f, 1f))
+ assertThat(values[4]).isEqualTo(0f)
+ }
+
+ @Test
+ fun lockscreenFadeOut_shadeExpanded() =
+ testScope.runTest {
+ val values by collectValues(underTest.lockscreenAlpha)
+ shadeExpanded(true)
+ runCurrent()
+
+ repository.sendTransitionSteps(
+ steps =
+ listOf(
+ step(0f, TransitionState.STARTED), // Should start running here...
+ step(0f),
+ step(.1f),
+ step(.4f),
+ step(.7f), // ...up to here
+ step(1f),
+ ),
+ testScope = testScope,
+ )
+ values.forEach { assertThat(it).isEqualTo(0f) }
}
@Test
@@ -115,7 +142,7 @@ class LockscreenToOccludedTransitionViewModelTest(flags: FlagsParameterization)
testScope.runTest {
configurationRepository.setDimensionPixelSize(
R.dimen.lockscreen_to_occluded_transition_lockscreen_translation_y,
- 100
+ 100,
)
val values by collectValues(underTest.lockscreenTranslationY)
repository.sendTransitionSteps(
@@ -138,7 +165,7 @@ class LockscreenToOccludedTransitionViewModelTest(flags: FlagsParameterization)
testScope.runTest {
configurationRepository.setDimensionPixelSize(
R.dimen.lockscreen_to_occluded_transition_lockscreen_translation_y,
- 100
+ 100,
)
val values by collectValues(underTest.lockscreenTranslationY)
repository.sendTransitionSteps(
@@ -171,7 +198,7 @@ class LockscreenToOccludedTransitionViewModelTest(flags: FlagsParameterization)
listOf(
step(0f, TransitionState.STARTED),
step(.5f),
- step(1f, TransitionState.FINISHED)
+ step(1f, TransitionState.FINISHED),
),
testScope = testScope,
)
@@ -228,7 +255,7 @@ class LockscreenToOccludedTransitionViewModelTest(flags: FlagsParameterization)
to = KeyguardState.OCCLUDED,
value = value,
transitionState = state,
- ownerName = "LockscreenToOccludedTransitionViewModelTest"
+ ownerName = "LockscreenToOccludedTransitionViewModelTest",
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelTest.kt
index a330cf01624f..fb1bf281715d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModelTest.kt
@@ -147,7 +147,7 @@ class LockscreenUserActionsViewModelTest : SysuiTestCase() {
}
}
- private fun expectedLeftDestination(
+ private fun expectedStartDestination(
isCommunalAvailable: Boolean,
isShadeTouchable: Boolean,
): SceneKey? {
@@ -246,17 +246,17 @@ class LockscreenUserActionsViewModelTest : SysuiTestCase() {
)
)
- val leftScene by
+ val startScene by
collectLastValue(
- (userActions?.get(Swipe.Left) as? UserActionResult.ChangeScene)?.toScene?.let {
- scene ->
- kosmos.sceneInteractor.resolveSceneFamily(scene)
- } ?: flowOf(null)
+ (userActions?.get(Swipe.Start) as? UserActionResult.ChangeScene)
+ ?.toScene
+ ?.let { scene -> kosmos.sceneInteractor.resolveSceneFamily(scene) }
+ ?: flowOf(null)
)
- assertThat(leftScene)
+ assertThat(startScene)
.isEqualTo(
- expectedLeftDestination(
+ expectedStartDestination(
isCommunalAvailable = isCommunalAvailable,
isShadeTouchable = isShadeTouchable,
)
@@ -341,17 +341,17 @@ class LockscreenUserActionsViewModelTest : SysuiTestCase() {
)
)
- val leftScene by
+ val startScene by
collectLastValue(
- (userActions?.get(Swipe.Left) as? UserActionResult.ChangeScene)?.toScene?.let {
- scene ->
- kosmos.sceneInteractor.resolveSceneFamily(scene)
- } ?: flowOf(null)
+ (userActions?.get(Swipe.Start) as? UserActionResult.ChangeScene)
+ ?.toScene
+ ?.let { scene -> kosmos.sceneInteractor.resolveSceneFamily(scene) }
+ ?: flowOf(null)
)
- assertThat(leftScene)
+ assertThat(startScene)
.isEqualTo(
- expectedLeftDestination(
+ expectedStartDestination(
isCommunalAvailable = isCommunalAvailable,
isShadeTouchable = isShadeTouchable,
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OffToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OffToLockscreenTransitionViewModelTest.kt
new file mode 100644
index 000000000000..0b7a38eb9ebd
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OffToLockscreenTransitionViewModelTest.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.keyguard.ui.viewmodel
+
+import android.platform.test.flag.junit.FlagsParameterization
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.andSceneContainer
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.collect.Range
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(ParameterizedAndroidJunit4::class)
+class OffToLockscreenTransitionViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
+ val kosmos = testKosmos()
+ val testScope = kosmos.testScope
+ val repository = kosmos.fakeKeyguardTransitionRepository
+ lateinit var underTest: OffToLockscreenTransitionViewModel
+
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf().andSceneContainer()
+ }
+ }
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags)
+ }
+
+ @Before
+ fun setup() {
+ underTest = kosmos.offToLockscreenTransitionViewModel
+ }
+
+ @Test
+ fun lockscreenAlpha() =
+ testScope.runTest {
+ val alpha by collectLastValue(underTest.lockscreenAlpha)
+
+ repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+ repository.sendTransitionStep(step(0f))
+ assertThat(alpha).isEqualTo(0f)
+
+ repository.sendTransitionStep(step(0.66f))
+ assertThat(alpha).isIn(Range.open(.1f, .9f))
+
+ repository.sendTransitionStep(step(1f))
+ assertThat(alpha).isEqualTo(1f)
+ }
+
+ private fun step(
+ value: Float,
+ state: TransitionState = TransitionState.RUNNING,
+ ): TransitionStep {
+ return TransitionStep(
+ from = KeyguardState.OFF,
+ to = KeyguardState.LOCKSCREEN,
+ value = value,
+ transitionState = state,
+ ownerName = "OffToLockscreenTransitionViewModelTest",
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
index 3e3aa4f079f7..e12c67b24893 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
@@ -18,7 +18,7 @@ import com.android.systemui.util.mockito.whenever
import com.android.wm.shell.recents.RecentTasks
import com.android.wm.shell.shared.GroupedRecentTaskInfo
import com.android.wm.shell.shared.split.SplitBounds
-import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_50_50
+import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50
import com.google.common.truth.Truth.assertThat
import java.util.Optional
import java.util.function.Consumer
@@ -268,7 +268,7 @@ class ShellRecentTaskListProviderTest : SysuiTestCase() {
GroupedRecentTaskInfo.forSplitTasks(
createTaskInfo(taskId1, userId1, isVisible),
createTaskInfo(taskId2, userId2, isVisible),
- SplitBounds(Rect(), Rect(), taskId1, taskId2, SNAP_TO_50_50)
+ SplitBounds(Rect(), Rect(), taskId1, taskId2, SNAP_TO_2_50_50)
)
private fun createTaskInfo(taskId: Int, userId: Int, isVisible: Boolean = false) =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartableTest.kt
index 8fe8878c6918..82fc0b58097c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartableTest.kt
@@ -25,7 +25,7 @@ import com.android.systemui.util.mockito.mock
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.verifyNoMoreInteractions
@RunWith(AndroidJUnit4::class)
@SmallTest
@@ -51,6 +51,6 @@ class MediaProjectionTaskSwitcherCoreStartableTest : SysuiTestCase() {
coreStartable.start()
- verifyZeroInteractions(coordinator)
+ verifyNoMoreInteractions(coordinator)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModelTest.kt
index 3388c75bcf78..ada2138d4d52 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModelTest.kt
@@ -21,19 +21,34 @@ import android.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
+import com.android.systemui.authentication.domain.interactor.AuthenticationResult
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
+import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.domain.startable.sceneContainerStartable
import com.android.systemui.scene.shared.model.Overlays
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.shared.flag.DualShade
import com.android.systemui.shade.ui.viewmodel.notificationsShadeOverlayContentViewModel
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
@@ -47,18 +62,84 @@ class NotificationsShadeOverlayContentViewModelTest : SysuiTestCase() {
private val underTest by lazy { kosmos.notificationsShadeOverlayContentViewModel }
+ @Before
+ fun setUp() {
+ kosmos.sceneContainerStartable.start()
+ underTest.activateIn(testScope)
+ }
+
@Test
fun onScrimClicked_hidesShade() =
testScope.runTest {
val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
- sceneInteractor.showOverlay(
- overlay = Overlays.NotificationsShade,
- loggingReason = "test",
- )
+ sceneInteractor.showOverlay(Overlays.NotificationsShade, "test")
assertThat(currentOverlays).contains(Overlays.NotificationsShade)
underTest.onScrimClicked()
assertThat(currentOverlays).doesNotContain(Overlays.NotificationsShade)
}
+
+ @Test
+ fun deviceLocked_hidesShade() =
+ testScope.runTest {
+ val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
+ unlockDevice()
+ sceneInteractor.showOverlay(Overlays.NotificationsShade, "test")
+ assertThat(currentOverlays).contains(Overlays.NotificationsShade)
+
+ lockDevice()
+
+ assertThat(currentOverlays).doesNotContain(Overlays.NotificationsShade)
+ }
+
+ @Test
+ fun bouncerShown_hidesShade() =
+ testScope.runTest {
+ val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
+ lockDevice()
+ sceneInteractor.showOverlay(Overlays.NotificationsShade, "test")
+ assertThat(currentOverlays).contains(Overlays.NotificationsShade)
+
+ sceneInteractor.changeScene(Scenes.Bouncer, "test")
+ runCurrent()
+
+ assertThat(currentOverlays).doesNotContain(Overlays.NotificationsShade)
+ }
+
+ @Test
+ fun shadeNotTouchable_hidesShade() =
+ testScope.runTest {
+ val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
+ val isShadeTouchable by collectLastValue(kosmos.shadeInteractor.isShadeTouchable)
+ assertThat(isShadeTouchable).isTrue()
+ sceneInteractor.showOverlay(Overlays.NotificationsShade, "test")
+ assertThat(currentOverlays).contains(Overlays.NotificationsShade)
+
+ lockDevice()
+ assertThat(isShadeTouchable).isFalse()
+ assertThat(currentOverlays).doesNotContain(Overlays.NotificationsShade)
+ }
+
+ private fun TestScope.lockDevice() {
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ kosmos.powerInteractor.setAsleepForTest()
+ runCurrent()
+
+ assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+ }
+
+ private suspend fun TestScope.unlockDevice() {
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ kosmos.powerInteractor.setAwakeForTest()
+ runCurrent()
+ assertThat(
+ kosmos.authenticationInteractor.authenticate(
+ FakeAuthenticationRepository.DEFAULT_PIN
+ )
+ )
+ .isEqualTo(AuthenticationResult.SUCCEEDED)
+
+ assertThat(currentScene).isEqualTo(Scenes.Gone)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSFooterViewControllerTest.java
index fb58b90d43f1..93dede5a3dae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSFooterViewControllerTest.java
@@ -22,7 +22,7 @@ import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.content.ClipData;
@@ -125,7 +125,7 @@ public class QSFooterViewControllerTest extends LeakCheckedTest {
mEditButton.performClick();
verify(mQSPanelController, never()).showEdit(any());
- verifyZeroInteractions(mActivityStarter);
+ verifyNoMoreInteractions(mActivityStarter);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSImplTest.java
index 4ce2d7c6d78b..fe1b963f6801 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/QSImplTest.java
@@ -36,7 +36,7 @@ import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.content.Context;
@@ -532,7 +532,7 @@ public class QSImplTest extends SysuiTestCase {
other.onComponentCreated(mQsComponent, null);
assertThat((View) other.getView().findViewById(R.id.qs_footer_actions)).isNull();
- verifyZeroInteractions(mFooterActionsViewModel, mFooterActionsViewModelFactory);
+ verifyNoMoreInteractions(mFooterActionsViewModel, mFooterActionsViewModelFactory);
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/AbstractQSFragmentComposeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/AbstractQSFragmentComposeViewModelTest.kt
new file mode 100644
index 000000000000..4bbdfa44e087
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/AbstractQSFragmentComposeViewModelTest.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.composefragment.viewmodel
+
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.testing.TestLifecycleOwner
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.testKosmos
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.TestResult
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.resetMain
+import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.test.setMain
+import org.junit.After
+import org.junit.Before
+
+@OptIn(ExperimentalCoroutinesApi::class)
+abstract class AbstractQSFragmentComposeViewModelTest : SysuiTestCase() {
+ protected val kosmos = testKosmos()
+
+ protected val lifecycleOwner =
+ TestLifecycleOwner(
+ initialState = Lifecycle.State.CREATED,
+ coroutineDispatcher = kosmos.testDispatcher,
+ )
+
+ protected val underTest by lazy {
+ kosmos.qsFragmentComposeViewModelFactory.create(lifecycleOwner.lifecycleScope)
+ }
+
+ @Before
+ fun setUp() {
+ Dispatchers.setMain(kosmos.testDispatcher)
+ }
+
+ @After
+ fun teardown() {
+ Dispatchers.resetMain()
+ }
+
+ protected inline fun TestScope.testWithinLifecycle(
+ crossinline block: suspend TestScope.() -> TestResult
+ ): TestResult {
+ return runTest {
+ lifecycleOwner.setCurrentState(Lifecycle.State.RESUMED)
+ lifecycleOwner.lifecycleScope.launch { underTest.activate() }
+ block().also { lifecycleOwner.setCurrentState(Lifecycle.State.DESTROYED) }
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelForceQSTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelForceQSTest.kt
new file mode 100644
index 000000000000..acd69af2736a
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelForceQSTest.kt
@@ -0,0 +1,98 @@
+/*
+ * 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.composefragment.viewmodel
+
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.sysuiStatusBarStateController
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@SmallTest
+@RunWith(Parameterized::class)
+@RunWithLooper
+class QSFragmentComposeViewModelForceQSTest(private val testData: TestData) :
+ AbstractQSFragmentComposeViewModelTest() {
+
+ @Test
+ fun forceQs_orRealExpansion() =
+ with(kosmos) {
+ testScope.testWithinLifecycle {
+ val expansionState by collectLastValue(underTest.expansionState)
+
+ with(testData) {
+ sysuiStatusBarStateController.setState(statusBarState)
+ underTest.isQSExpanded = expanded
+ underTest.isStackScrollerOverscrolling = stackScrollerOverScrolling
+ fakeDeviceEntryRepository.setBypassEnabled(bypassEnabled)
+ underTest.isTransitioningToFullShade = transitioningToFullShade
+ underTest.isInSplitShade = inSplitShade
+
+ underTest.qsExpansionValue = EXPANSION
+ assertThat(expansionState!!.progress)
+ .isEqualTo(if (expectedForceQS) 1f else EXPANSION)
+ }
+ }
+ }
+
+ data class TestData(
+ val statusBarState: Int,
+ val expanded: Boolean,
+ val stackScrollerOverScrolling: Boolean,
+ val bypassEnabled: Boolean,
+ val transitioningToFullShade: Boolean,
+ val inSplitShade: Boolean,
+ ) {
+ private val inKeyguard = statusBarState == StatusBarState.KEYGUARD
+
+ private val showCollapsedOnKeyguard =
+ bypassEnabled || (transitioningToFullShade && !inSplitShade)
+
+ val expectedForceQS =
+ (expanded || stackScrollerOverScrolling) && (inKeyguard && !showCollapsedOnKeyguard)
+ }
+
+ companion object {
+ private const val EXPANSION = 0.3f
+
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun createTestData(): List<TestData> {
+ return statusBarStates.flatMap { statusBarState ->
+ (0u..31u).map { bitfield ->
+ TestData(
+ statusBarState,
+ expanded = (bitfield and 1u) == 1u,
+ stackScrollerOverScrolling = (bitfield and 2u) == 2u,
+ bypassEnabled = (bitfield and 4u) == 4u,
+ transitioningToFullShade = (bitfield and 8u) == 8u,
+ inSplitShade = (bitfield and 16u) == 16u,
+ )
+ }
+ }
+ }
+
+ private val statusBarStates =
+ setOf(StatusBarState.SHADE, StatusBarState.KEYGUARD, StatusBarState.SHADE_LOCKED)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt
index 6f20e70f84a8..c19e4b834c7c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt
@@ -19,64 +19,28 @@ package com.android.systemui.qs.composefragment.viewmodel
import android.app.StatusBarManager
import android.content.testableContext
import android.testing.TestableLooper.RunWithLooper
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.lifecycleScope
-import androidx.lifecycle.testing.TestLifecycleOwner
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.qs.fgsManagerController
+import com.android.systemui.qs.panels.domain.interactor.tileSquishinessInteractor
import com.android.systemui.res.R
import com.android.systemui.shade.largeScreenHeaderHelper
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel
import com.android.systemui.statusbar.disableflags.data.repository.fakeDisableFlagsRepository
import com.android.systemui.statusbar.sysuiStatusBarStateController
-import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestResult
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import kotlinx.coroutines.test.setMain
-import org.junit.After
-import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
@RunWithLooper
-@OptIn(ExperimentalCoroutinesApi::class)
-class QSFragmentComposeViewModelTest : SysuiTestCase() {
- private val kosmos = testKosmos()
-
- private val lifecycleOwner =
- TestLifecycleOwner(
- initialState = Lifecycle.State.CREATED,
- coroutineDispatcher = kosmos.testDispatcher,
- )
-
- private val underTest by lazy {
- kosmos.qsFragmentComposeViewModelFactory.create(lifecycleOwner.lifecycleScope)
- }
-
- @Before
- fun setUp() {
- Dispatchers.setMain(kosmos.testDispatcher)
- }
-
- @After
- fun teardown() {
- Dispatchers.resetMain()
- }
+class QSFragmentComposeViewModelTest : AbstractQSFragmentComposeViewModelTest() {
@Test
fun qsExpansionValueChanges_correctExpansionState() =
@@ -205,16 +169,30 @@ class QSFragmentComposeViewModelTest : SysuiTestCase() {
}
}
- private inline fun TestScope.testWithinLifecycle(
- crossinline block: suspend TestScope.() -> TestResult
- ): TestResult {
- return runTest {
- lifecycleOwner.setCurrentState(Lifecycle.State.RESUMED)
- block().also { lifecycleOwner.setCurrentState(Lifecycle.State.DESTROYED) }
+ @Test
+ fun squishinessInExpansion_setInInteractor() =
+ with(kosmos) {
+ testScope.testWithinLifecycle {
+ val squishiness by collectLastValue(tileSquishinessInteractor.squishiness)
+
+ underTest.squishinessFractionValue = 0.3f
+ assertThat(squishiness).isWithin(epsilon).of(0.3f.constrainSquishiness())
+
+ underTest.squishinessFractionValue = 0f
+ assertThat(squishiness).isWithin(epsilon).of(0f.constrainSquishiness())
+
+ underTest.squishinessFractionValue = 1f
+ assertThat(squishiness).isWithin(epsilon).of(1f.constrainSquishiness())
+ }
}
- }
companion object {
private const val QS_DISABLE_FLAG = StatusBarManager.DISABLE2_QUICK_SETTINGS
+
+ private fun Float.constrainSquishiness(): Float {
+ return (0.1f + this * 0.9f).coerceIn(0f, 1f)
+ }
+
+ private const val epsilon = 0.001f
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayoutTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayoutTest.kt
index 9e90090549dd..a9a527fb8df6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayoutTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayoutTest.kt
@@ -22,10 +22,8 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.kosmos.testScope
import com.android.systemui.qs.panels.data.repository.DefaultLargeTilesRepository
import com.android.systemui.qs.panels.data.repository.defaultLargeTilesRepository
-import com.android.systemui.qs.panels.ui.compose.infinitegrid.InfiniteGridLayout
+import com.android.systemui.qs.panels.domain.interactor.infiniteGridLayout
import com.android.systemui.qs.panels.ui.viewmodel.MockTileViewModel
-import com.android.systemui.qs.panels.ui.viewmodel.fixedColumnsSizeViewModel
-import com.android.systemui.qs.panels.ui.viewmodel.iconTilesViewModel
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
@@ -44,8 +42,7 @@ class InfiniteGridLayoutTest : SysuiTestCase() {
}
}
- private val underTest =
- with(kosmos) { InfiniteGridLayout(iconTilesViewModel, fixedColumnsSizeViewModel) }
+ private val underTest = kosmos.infiniteGridLayout
@Test
fun correctPagination_underOnePage_sameOrder() =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
index 2580ac2c8da7..7798f46fdb46 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
@@ -14,6 +14,8 @@
package com.android.systemui.qs.tileimpl;
+import static com.android.systemui.Flags.FLAG_QS_NEW_TILES;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.mockito.ArgumentMatchers.any;
@@ -21,11 +23,16 @@ import static org.mockito.ArgumentMatchers.argThat;
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;
import static org.mockito.Mockito.when;
+import android.content.Context;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.Drawable;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.service.quicksettings.Tile;
import android.testing.UiThreadTest;
import android.widget.ImageView;
@@ -47,7 +54,6 @@ import org.mockito.Mockito;
@UiThreadTest
@SmallTest
public class QSIconViewImplTest extends SysuiTestCase {
-
private QSIconViewImpl mIconView;
@Before
@@ -106,6 +112,34 @@ public class QSIconViewImplTest extends SysuiTestCase {
verify(iv).setImageTintList(argThat(stateList -> stateList.getColors()[0] == desiredColor));
}
+
+ @EnableFlags(FLAG_QS_NEW_TILES)
+ @Test
+ public void testIconPreloaded_withFlagOn_immediatelyLoadsAll3TintColors() {
+ Context ctx = spy(mContext);
+
+ QSIconViewImpl iconView = new QSIconViewImpl(ctx);
+
+ verify(ctx, times(3)).obtainStyledAttributes(any());
+
+ iconView.getColor(new State()); // this should not increase the call count
+
+ verify(ctx, times(3)).obtainStyledAttributes(any());
+ }
+
+ @DisableFlags(FLAG_QS_NEW_TILES)
+ @Test
+ public void testIconPreloaded_withFlagOff_loadsOneTintColorAfterIconColorIsRead() {
+ Context ctx = spy(mContext);
+ QSIconViewImpl iconView = new QSIconViewImpl(ctx);
+
+ verify(ctx, never()).obtainStyledAttributes(any()); // none of the colors are preloaded
+
+ iconView.getColor(new State());
+
+ verify(ctx, times(1)).obtainStyledAttributes(any());
+ }
+
@Test
public void testStateSetCorrectly_toString() {
ImageView iv = mock(ImageView.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
index 418d126ce3cf..940da9967a68 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
@@ -64,7 +64,7 @@ import org.mockito.Mockito.doNothing
import org.mockito.Mockito.nullable
import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
import java.util.Optional
@@ -320,7 +320,7 @@ class DeviceControlsTileTest : SysuiTestCase() {
tile.click(null /* view */)
testableLooper.processAllMessages()
- verifyZeroInteractions(activityStarter)
+ verifyNoMoreInteractions(activityStarter)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
index 914bd0e13dc6..0729e2fcd35f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
@@ -33,7 +33,7 @@ import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.app.PendingIntent;
@@ -462,7 +462,7 @@ public class QuickAccessWalletTileTest extends SysuiTestCase {
public void testHandleSetListening_notListening_notQueryCards() {
mTile.handleSetListening(false);
- verifyZeroInteractions(mQuickAccessWalletClient);
+ verifyNoMoreInteractions(mQuickAccessWalletClient);
}
private WalletCard createWalletCardWithType(Context context, int cardType) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt
index 620e90dcaa62..d32ba47204c0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt
@@ -17,13 +17,17 @@
package com.android.systemui.qs.tiles.impl.internet.domain
import android.graphics.drawable.TestStubDrawable
+import android.os.fakeExecutorHandler
import android.widget.Switch
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.settingslib.graph.SignalDrawable
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.shared.model.Text
+import com.android.systemui.common.shared.model.Text.Companion.loadText
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject
import com.android.systemui.qs.tiles.impl.internet.domain.model.InternetTileModel
@@ -31,6 +35,9 @@ import com.android.systemui.qs.tiles.impl.internet.qsInternetTileConfig
import com.android.systemui.qs.tiles.viewmodel.QSTileState
import com.android.systemui.res.R
import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_FULL_ICONS
+import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
+import com.android.systemui.statusbar.pipeline.satellite.ui.model.SatelliteIconModel
+import com.android.systemui.statusbar.pipeline.shared.ui.model.InternetTileIconModel
import org.junit.Test
import org.junit.runner.RunWith
@@ -39,25 +46,93 @@ import org.junit.runner.RunWith
class InternetTileMapperTest : SysuiTestCase() {
private val kosmos = Kosmos()
private val internetTileConfig = kosmos.qsInternetTileConfig
+ private val handler = kosmos.fakeExecutorHandler
private val mapper by lazy {
InternetTileMapper(
context.orCreateTestableResources
.apply {
addOverride(R.drawable.ic_qs_no_internet_unavailable, TestStubDrawable())
+ addOverride(R.drawable.ic_satellite_connected_2, TestStubDrawable())
addOverride(wifiRes, TestStubDrawable())
}
.resources,
context.theme,
- context
+ context,
+ handler,
)
}
@Test
- fun withActiveModel_mappedStateMatchesDataModel() {
+ fun withActiveCellularModel_mappedStateMatchesDataModel() {
val inputModel =
InternetTileModel.Active(
secondaryLabel = Text.Resource(R.string.quick_settings_networks_available),
- iconId = wifiRes,
+ icon = InternetTileIconModel.Cellular(3),
+ stateDescription = null,
+ contentDescription =
+ ContentDescription.Resource(R.string.quick_settings_internet_label),
+ )
+
+ val outputState = mapper.map(internetTileConfig, inputModel)
+
+ val signalDrawable = SignalDrawable(context, handler)
+ signalDrawable.setLevel(3)
+ val expectedState =
+ createInternetTileState(
+ QSTileState.ActivationState.ACTIVE,
+ context.getString(R.string.quick_settings_networks_available),
+ Icon.Loaded(signalDrawable, null),
+ null,
+ context.getString(R.string.quick_settings_internet_label),
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun withActiveSatelliteModel_mappedStateMatchesDataModel() {
+ val inputIcon =
+ SignalIconModel.Satellite(
+ 3,
+ Icon.Resource(
+ res = R.drawable.ic_satellite_connected_2,
+ contentDescription =
+ ContentDescription.Resource(
+ R.string.accessibility_status_bar_satellite_good_connection
+ ),
+ ),
+ )
+ val inputModel =
+ InternetTileModel.Active(
+ secondaryLabel = Text.Resource(R.string.quick_settings_networks_available),
+ icon = InternetTileIconModel.Satellite(inputIcon.icon),
+ stateDescription = null,
+ contentDescription =
+ ContentDescription.Resource(
+ R.string.accessibility_status_bar_satellite_good_connection
+ ),
+ )
+
+ val outputState = mapper.map(internetTileConfig, inputModel)
+
+ val expectedSatIcon = SatelliteIconModel.fromSignalStrength(3)
+
+ val expectedState =
+ createInternetTileState(
+ QSTileState.ActivationState.ACTIVE,
+ inputModel.secondaryLabel.loadText(context).toString(),
+ Icon.Loaded(context.getDrawable(expectedSatIcon!!.res)!!, null),
+ expectedSatIcon.res,
+ expectedSatIcon.contentDescription.loadContentDescription(context).toString(),
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun withActiveWifiModel_mappedStateMatchesDataModel() {
+ val inputModel =
+ InternetTileModel.Active(
+ secondaryLabel = Text.Resource(R.string.quick_settings_networks_available),
+ icon = InternetTileIconModel.ResourceId(wifiRes),
stateDescription = null,
contentDescription =
ContentDescription.Resource(R.string.quick_settings_internet_label),
@@ -71,7 +146,7 @@ class InternetTileMapperTest : SysuiTestCase() {
context.getString(R.string.quick_settings_networks_available),
Icon.Loaded(context.getDrawable(wifiRes)!!, contentDescription = null),
wifiRes,
- context.getString(R.string.quick_settings_internet_label)
+ context.getString(R.string.quick_settings_internet_label),
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
@@ -81,7 +156,7 @@ class InternetTileMapperTest : SysuiTestCase() {
val inputModel =
InternetTileModel.Inactive(
secondaryLabel = Text.Resource(R.string.quick_settings_networks_unavailable),
- iconId = R.drawable.ic_qs_no_internet_unavailable,
+ icon = InternetTileIconModel.ResourceId(R.drawable.ic_qs_no_internet_unavailable),
stateDescription = null,
contentDescription =
ContentDescription.Resource(R.string.quick_settings_networks_unavailable),
@@ -95,10 +170,10 @@ class InternetTileMapperTest : SysuiTestCase() {
context.getString(R.string.quick_settings_networks_unavailable),
Icon.Loaded(
context.getDrawable(R.drawable.ic_qs_no_internet_unavailable)!!,
- contentDescription = null
+ contentDescription = null,
),
R.drawable.ic_qs_no_internet_unavailable,
- context.getString(R.string.quick_settings_networks_unavailable)
+ context.getString(R.string.quick_settings_networks_unavailable),
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
@@ -107,7 +182,7 @@ class InternetTileMapperTest : SysuiTestCase() {
activationState: QSTileState.ActivationState,
secondaryLabel: String,
icon: Icon,
- iconRes: Int,
+ iconRes: Int? = null,
contentDescription: String,
): QSTileState {
val label = context.getString(R.string.quick_settings_internet_label)
@@ -120,13 +195,13 @@ class InternetTileMapperTest : SysuiTestCase() {
setOf(
QSTileState.UserAction.CLICK,
QSTileState.UserAction.TOGGLE_CLICK,
- QSTileState.UserAction.LONG_CLICK
+ QSTileState.UserAction.LONG_CLICK,
),
contentDescription,
null,
QSTileState.SideViewIcon.Chevron,
QSTileState.EnabledState.ENABLED,
- Switch::class.qualifiedName
+ Switch::class.qualifiedName,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractorTest.kt
index 5a4506086058..5259aa84b193 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractorTest.kt
@@ -18,14 +18,12 @@ package com.android.systemui.qs.tiles.impl.internet.domain.interactor
import android.graphics.drawable.TestStubDrawable
import android.os.UserHandle
-import android.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.settingslib.AccessibilityContentDescriptions
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
-import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.shared.model.Text
import com.android.systemui.common.shared.model.Text.Companion.loadText
import com.android.systemui.coroutines.collectLastValue
@@ -49,6 +47,7 @@ import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIc
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.statusbar.pipeline.shared.ui.model.InternetTileIconModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
@@ -60,9 +59,7 @@ import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
-import org.junit.Assume.assumeFalse
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -144,7 +141,6 @@ class InternetTileDataInteractorTest : SysuiTestCase() {
underTest =
InternetTileDataInteractor(
context,
- testScope.coroutineContext,
testScope.backgroundScope,
airplaneModeRepository,
connectivityRepository,
@@ -164,9 +160,11 @@ class InternetTileDataInteractorTest : SysuiTestCase() {
connectivityRepository.defaultConnections.value = DefaultConnectionModel()
+ val expectedIcon =
+ InternetTileIconModel.ResourceId(R.drawable.ic_qs_no_internet_unavailable)
assertThat(latest?.secondaryLabel)
.isEqualTo(Text.Resource(R.string.quick_settings_networks_unavailable))
- assertThat(latest?.iconId).isEqualTo(R.drawable.ic_qs_no_internet_unavailable)
+ assertThat(latest?.icon).isEqualTo(expectedIcon)
}
@Test
@@ -183,11 +181,8 @@ class InternetTileDataInteractorTest : SysuiTestCase() {
underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
)
- val networkModel =
- WifiNetworkModel.Active.of(
- level = 4,
- ssid = "test ssid",
- )
+ val networkModel = WifiNetworkModel.Active.of(level = 4, ssid = "test ssid")
+
val wifiIcon =
WifiIcon.fromModel(model = networkModel, context = context, showHotspotInfo = true)
as WifiIcon.Visible
@@ -198,12 +193,9 @@ class InternetTileDataInteractorTest : SysuiTestCase() {
assertThat(latest?.secondaryTitle).isEqualTo("test ssid")
assertThat(latest?.secondaryLabel).isNull()
- val expectedIcon =
- Icon.Loaded(context.getDrawable(WifiIcons.WIFI_NO_INTERNET_ICONS[4])!!, null)
- val actualIcon = latest?.icon
- assertThat(actualIcon).isEqualTo(expectedIcon)
- assertThat(latest?.iconId).isEqualTo(WifiIcons.WIFI_NO_INTERNET_ICONS[4])
+ val expectedIcon = InternetTileIconModel.ResourceId(WifiIcons.WIFI_NO_INTERNET_ICONS[4])
+ assertThat(latest?.icon).isEqualTo(expectedIcon)
assertThat(latest?.contentDescription.loadContentDescription(context))
.isEqualTo("$internet,test ssid")
val expectedSd = wifiIcon.contentDescription
@@ -229,8 +221,7 @@ class InternetTileDataInteractorTest : SysuiTestCase() {
wifiRepository.setIsWifiDefault(true)
wifiRepository.setWifiNetwork(networkModel)
- val expectedIcon =
- Icon.Loaded(context.getDrawable(WifiIcons.WIFI_NO_INTERNET_ICONS[4])!!, null)
+ val expectedIcon = InternetTileIconModel.ResourceId(WifiIcons.WIFI_NO_INTERNET_ICONS[4])
assertThat(latest?.icon).isEqualTo(expectedIcon)
assertThat(latest?.stateDescription.loadContentDescription(context))
.doesNotContain(
@@ -249,9 +240,8 @@ class InternetTileDataInteractorTest : SysuiTestCase() {
setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.TABLET)
val expectedIcon =
- Icon.Loaded(
- context.getDrawable(com.android.settingslib.R.drawable.ic_hotspot_tablet)!!,
- null
+ InternetTileIconModel.ResourceId(
+ com.android.settingslib.R.drawable.ic_hotspot_tablet
)
assertThat(latest?.icon).isEqualTo(expectedIcon)
assertThat(latest?.stateDescription.loadContentDescription(context))
@@ -271,9 +261,8 @@ class InternetTileDataInteractorTest : SysuiTestCase() {
setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.LAPTOP)
val expectedIcon =
- Icon.Loaded(
- context.getDrawable(com.android.settingslib.R.drawable.ic_hotspot_laptop)!!,
- null
+ InternetTileIconModel.ResourceId(
+ com.android.settingslib.R.drawable.ic_hotspot_laptop
)
assertThat(latest?.icon).isEqualTo(expectedIcon)
assertThat(latest?.stateDescription.loadContentDescription(context))
@@ -293,10 +282,10 @@ class InternetTileDataInteractorTest : SysuiTestCase() {
setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.WATCH)
val expectedIcon =
- Icon.Loaded(
- context.getDrawable(com.android.settingslib.R.drawable.ic_hotspot_watch)!!,
- null
+ InternetTileIconModel.ResourceId(
+ com.android.settingslib.R.drawable.ic_hotspot_watch
)
+
assertThat(latest?.icon).isEqualTo(expectedIcon)
assertThat(latest?.stateDescription.loadContentDescription(context))
.isEqualTo(
@@ -315,10 +304,7 @@ class InternetTileDataInteractorTest : SysuiTestCase() {
setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.AUTO)
val expectedIcon =
- Icon.Loaded(
- context.getDrawable(com.android.settingslib.R.drawable.ic_hotspot_auto)!!,
- null
- )
+ InternetTileIconModel.ResourceId(com.android.settingslib.R.drawable.ic_hotspot_auto)
assertThat(latest?.icon).isEqualTo(expectedIcon)
assertThat(latest?.stateDescription.loadContentDescription(context))
.isEqualTo(
@@ -336,9 +322,8 @@ class InternetTileDataInteractorTest : SysuiTestCase() {
setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.PHONE)
val expectedIcon =
- Icon.Loaded(
- context.getDrawable(com.android.settingslib.R.drawable.ic_hotspot_phone)!!,
- null
+ InternetTileIconModel.ResourceId(
+ com.android.settingslib.R.drawable.ic_hotspot_phone
)
assertThat(latest?.icon).isEqualTo(expectedIcon)
assertThat(latest?.stateDescription.loadContentDescription(context))
@@ -358,9 +343,8 @@ class InternetTileDataInteractorTest : SysuiTestCase() {
setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.UNKNOWN)
val expectedIcon =
- Icon.Loaded(
- context.getDrawable(com.android.settingslib.R.drawable.ic_hotspot_phone)!!,
- null
+ InternetTileIconModel.ResourceId(
+ com.android.settingslib.R.drawable.ic_hotspot_phone
)
assertThat(latest?.icon).isEqualTo(expectedIcon)
assertThat(latest?.stateDescription.loadContentDescription(context))
@@ -380,10 +364,10 @@ class InternetTileDataInteractorTest : SysuiTestCase() {
setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.INVALID)
val expectedIcon =
- Icon.Loaded(
- context.getDrawable(com.android.settingslib.R.drawable.ic_hotspot_phone)!!,
- null
+ InternetTileIconModel.ResourceId(
+ com.android.settingslib.R.drawable.ic_hotspot_phone
)
+
assertThat(latest?.icon).isEqualTo(expectedIcon)
assertThat(latest?.stateDescription.loadContentDescription(context))
.isEqualTo(
@@ -426,8 +410,9 @@ class InternetTileDataInteractorTest : SysuiTestCase() {
assertThat(latest?.secondaryLabel).isNull()
assertThat(latest?.secondaryTitle)
.isEqualTo(context.getString(R.string.quick_settings_networks_available))
- assertThat(latest?.icon).isNull()
- assertThat(latest?.iconId).isEqualTo(R.drawable.ic_qs_no_internet_available)
+ val expectedIcon =
+ InternetTileIconModel.ResourceId(R.drawable.ic_qs_no_internet_available)
+ assertThat(latest?.icon).isEqualTo(expectedIcon)
assertThat(latest?.stateDescription).isNull()
val expectedCd =
"$internet,${context.getString(R.string.quick_settings_networks_available)}"
@@ -435,54 +420,19 @@ class InternetTileDataInteractorTest : SysuiTestCase() {
.isEqualTo(expectedCd)
}
- /**
- * We expect a RuntimeException because [underTest] instantiates a SignalDrawable on the
- * provided context, and so the SignalDrawable constructor attempts to instantiate a Handler()
- * on the mentioned context. Since that context does not have a looper assigned to it, the
- * handler instantiation will throw a RuntimeException.
- *
- * TODO(b/338068066): Robolectric behavior differs in that it does not throw the exception So
- * either we should make Robolectric behave similar to the device test, or change this test to
- * look for a different signal than the exception, when run by Robolectric. For now we just
- * assume the test is not Robolectric.
- */
- @Test(expected = java.lang.RuntimeException::class)
- fun mobileDefault_usesNetworkNameAndIcon_throwsRunTimeException() =
- testScope.runTest {
- assumeFalse(isRobolectricTest())
-
- collectLastValue(underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest)))
-
- connectivityRepository.setMobileConnected()
- mobileConnectionsRepository.mobileIsDefault.value = true
- mobileConnectionRepository.apply {
- setAllLevels(3)
- setAllRoaming(false)
- networkName.value = NetworkNameModel.Default("test network")
- }
-
- runCurrent()
- }
-
- /**
- * See [mobileDefault_usesNetworkNameAndIcon_throwsRunTimeException] for description of the
- * problem this test solves. The solution here is to assign a looper to the context via
- * RunWithLooper. In the production code, the solution is to use a Main CoroutineContext for
- * creating the SignalDrawable.
- */
- @TestableLooper.RunWithLooper
@Test
- fun mobileDefault_run_withLooper_usesNetworkNameAndIcon() =
+ fun mobileDefault_usesNetworkNameAndIcon() =
testScope.runTest {
val latest by
collectLastValue(
underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
)
+ val iconLevel = 3
connectivityRepository.setMobileConnected()
mobileConnectionsRepository.mobileIsDefault.value = true
mobileConnectionRepository.apply {
- setAllLevels(3)
+ setAllLevels(iconLevel)
setAllRoaming(false)
networkName.value = NetworkNameModel.Default("test network")
}
@@ -491,8 +441,9 @@ class InternetTileDataInteractorTest : SysuiTestCase() {
assertThat(latest?.secondaryTitle).isNotNull()
assertThat(latest?.secondaryTitle.toString()).contains("test network")
assertThat(latest?.secondaryLabel).isNull()
- assertThat(latest?.icon).isInstanceOf(Icon.Loaded::class.java)
- assertThat(latest?.iconId).isNull()
+ val expectedIcon = InternetTileIconModel.Cellular(iconLevel)
+
+ assertThat(latest?.icon).isEqualTo(expectedIcon)
assertThat(latest?.stateDescription.loadContentDescription(context))
.isEqualTo(latest?.secondaryTitle.toString())
assertThat(latest?.contentDescription.loadContentDescription(context))
@@ -513,8 +464,8 @@ class InternetTileDataInteractorTest : SysuiTestCase() {
assertThat(latest?.secondaryLabel.loadText(context))
.isEqualTo(ethernetIcon!!.contentDescription.loadContentDescription(context))
assertThat(latest?.secondaryTitle).isNull()
- assertThat(latest?.iconId).isEqualTo(R.drawable.stat_sys_ethernet_fully)
- assertThat(latest?.icon).isNull()
+ val expectedIcon = InternetTileIconModel.ResourceId(R.drawable.stat_sys_ethernet_fully)
+ assertThat(latest?.icon).isEqualTo(expectedIcon)
assertThat(latest?.stateDescription).isNull()
assertThat(latest?.contentDescription.loadContentDescription(context))
.isEqualTo(latest?.secondaryLabel.loadText(context))
@@ -534,8 +485,8 @@ class InternetTileDataInteractorTest : SysuiTestCase() {
assertThat(latest?.secondaryLabel.loadText(context))
.isEqualTo(ethernetIcon!!.contentDescription.loadContentDescription(context))
assertThat(latest?.secondaryTitle).isNull()
- assertThat(latest?.iconId).isEqualTo(R.drawable.stat_sys_ethernet)
- assertThat(latest?.icon).isNull()
+ val expectedIcon = InternetTileIconModel.ResourceId(R.drawable.stat_sys_ethernet)
+ assertThat(latest?.icon).isEqualTo(expectedIcon)
assertThat(latest?.stateDescription).isNull()
assertThat(latest?.contentDescription.loadContentDescription(context))
.isEqualTo(latest?.secondaryLabel.loadText(context))
@@ -543,11 +494,7 @@ class InternetTileDataInteractorTest : SysuiTestCase() {
private fun setWifiNetworkWithHotspot(hotspot: WifiNetworkModel.HotspotDeviceType) {
val networkModel =
- WifiNetworkModel.Active.of(
- level = 4,
- ssid = "test ssid",
- hotspotDeviceType = hotspot,
- )
+ WifiNetworkModel.Active.of(level = 4, ssid = "test ssid", hotspotDeviceType = hotspot)
connectivityRepository.setWifiConnected()
wifiRepository.setIsWifiDefault(true)
@@ -560,7 +507,7 @@ class InternetTileDataInteractorTest : SysuiTestCase() {
val NOT_CONNECTED_NETWORKS_UNAVAILABLE =
InternetTileModel.Inactive(
secondaryLabel = Text.Resource(R.string.quick_settings_networks_unavailable),
- iconId = R.drawable.ic_qs_no_internet_unavailable,
+ icon = InternetTileIconModel.ResourceId(R.drawable.ic_qs_no_internet_unavailable),
stateDescription = null,
contentDescription =
ContentDescription.Resource(R.string.quick_settings_networks_unavailable),
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt
index 8c7ec4743e7c..f32894dfd383 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt
@@ -21,18 +21,33 @@ import android.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
+import com.android.systemui.authentication.domain.interactor.AuthenticationResult
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
+import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.domain.startable.sceneContainerStartable
import com.android.systemui.scene.shared.model.Overlays
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.shared.flag.DualShade
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
@@ -46,18 +61,84 @@ class QuickSettingsShadeOverlayContentViewModelTest : SysuiTestCase() {
private val underTest by lazy { kosmos.quickSettingsShadeOverlayContentViewModel }
+ @Before
+ fun setUp() {
+ kosmos.sceneContainerStartable.start()
+ underTest.activateIn(testScope)
+ }
+
@Test
fun onScrimClicked_hidesShade() =
testScope.runTest {
val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
- sceneInteractor.showOverlay(
- overlay = Overlays.QuickSettingsShade,
- loggingReason = "test",
- )
+ sceneInteractor.showOverlay(Overlays.QuickSettingsShade, "test")
assertThat(currentOverlays).contains(Overlays.QuickSettingsShade)
underTest.onScrimClicked()
assertThat(currentOverlays).doesNotContain(Overlays.QuickSettingsShade)
}
+
+ @Test
+ fun deviceLocked_hidesShade() =
+ testScope.runTest {
+ val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
+ unlockDevice()
+ sceneInteractor.showOverlay(Overlays.QuickSettingsShade, "test")
+ assertThat(currentOverlays).contains(Overlays.QuickSettingsShade)
+
+ lockDevice()
+
+ assertThat(currentOverlays).isEmpty()
+ }
+
+ @Test
+ fun bouncerShown_hidesShade() =
+ testScope.runTest {
+ val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
+ lockDevice()
+ sceneInteractor.showOverlay(Overlays.QuickSettingsShade, "test")
+ assertThat(currentOverlays).contains(Overlays.QuickSettingsShade)
+
+ sceneInteractor.changeScene(Scenes.Bouncer, "test")
+ runCurrent()
+
+ assertThat(currentOverlays).doesNotContain(Overlays.QuickSettingsShade)
+ }
+
+ @Test
+ fun shadeNotTouchable_hidesShade() =
+ testScope.runTest {
+ val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
+ val isShadeTouchable by collectLastValue(kosmos.shadeInteractor.isShadeTouchable)
+ assertThat(isShadeTouchable).isTrue()
+ sceneInteractor.showOverlay(Overlays.QuickSettingsShade, "test")
+ assertThat(currentOverlays).contains(Overlays.QuickSettingsShade)
+
+ lockDevice()
+ assertThat(isShadeTouchable).isFalse()
+ assertThat(currentOverlays).doesNotContain(Overlays.QuickSettingsShade)
+ }
+
+ private fun TestScope.lockDevice() {
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ kosmos.powerInteractor.setAsleepForTest()
+ runCurrent()
+
+ assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+ }
+
+ private suspend fun TestScope.unlockDevice() {
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ kosmos.powerInteractor.setAwakeForTest()
+ runCurrent()
+ assertThat(
+ kosmos.authenticationInteractor.authenticate(
+ FakeAuthenticationRepository.DEFAULT_PIN
+ )
+ )
+ .isEqualTo(AuthenticationResult.SUCCEEDED)
+
+ assertThat(currentScene).isEqualTo(Scenes.Gone)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/IssueRecordingServiceCommandHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/IssueRecordingServiceSessionTest.kt
index 57cfe1b9e902..3e5dee69c85c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/IssueRecordingServiceCommandHandlerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/IssueRecordingServiceSessionTest.kt
@@ -47,7 +47,7 @@ import org.mockito.kotlin.verify
@SmallTest
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
-class IssueRecordingServiceCommandHandlerTest : SysuiTestCase() {
+class IssueRecordingServiceSessionTest : SysuiTestCase() {
private val kosmos = Kosmos().also { it.testCase = this }
private val bgExecutor = kosmos.fakeExecutor
@@ -61,13 +61,13 @@ class IssueRecordingServiceCommandHandlerTest : SysuiTestCase() {
private val notificationManager = mock<NotificationManager>()
private val panelInteractor = mock<PanelInteractor>()
- private lateinit var underTest: IssueRecordingServiceCommandHandler
+ private lateinit var underTest: IssueRecordingServiceSession
@Before
fun setup() {
traceurMessageSender = mock<TraceurMessageSender>()
underTest =
- IssueRecordingServiceCommandHandler(
+ IssueRecordingServiceSession(
bgExecutor,
dialogTransitionAnimator,
panelInteractor,
@@ -75,13 +75,13 @@ class IssueRecordingServiceCommandHandlerTest : SysuiTestCase() {
issueRecordingState,
iActivityManager,
notificationManager,
- userContextProvider
+ userContextProvider,
)
}
@Test
fun startsTracing_afterReceivingActionStartCommand() {
- underTest.handleStartCommand()
+ underTest.start()
bgExecutor.runAllReady()
Truth.assertThat(issueRecordingState.isRecording).isTrue()
@@ -90,7 +90,7 @@ class IssueRecordingServiceCommandHandlerTest : SysuiTestCase() {
@Test
fun stopsTracing_afterReceivingStopTracingCommand() {
- underTest.handleStopCommand(mContext.contentResolver)
+ underTest.stop(mContext.contentResolver)
bgExecutor.runAllReady()
Truth.assertThat(issueRecordingState.isRecording).isFalse()
@@ -99,7 +99,7 @@ class IssueRecordingServiceCommandHandlerTest : SysuiTestCase() {
@Test
fun cancelsNotification_afterReceivingShareCommand() {
- underTest.handleShareCommand(0, null, mContext)
+ underTest.share(0, null, mContext)
bgExecutor.runAllReady()
verify(notificationManager).cancelAsUser(isNull(), anyInt(), any<UserHandle>())
@@ -110,7 +110,7 @@ class IssueRecordingServiceCommandHandlerTest : SysuiTestCase() {
issueRecordingState.takeBugreport = true
val uri = mock<Uri>()
- underTest.handleShareCommand(0, uri, mContext)
+ underTest.share(0, uri, mContext)
bgExecutor.runAllReady()
verify(iActivityManager).requestBugReportWithExtraAttachment(uri)
@@ -121,7 +121,7 @@ class IssueRecordingServiceCommandHandlerTest : SysuiTestCase() {
issueRecordingState.takeBugreport = false
val uri = mock<Uri>()
- underTest.handleShareCommand(0, uri, mContext)
+ underTest.share(0, uri, mContext)
bgExecutor.runAllReady()
verify(traceurMessageSender).shareTraces(mContext, uri)
@@ -131,7 +131,7 @@ class IssueRecordingServiceCommandHandlerTest : SysuiTestCase() {
fun closesShade_afterReceivingShareCommand() {
val uri = mock<Uri>()
- underTest.handleShareCommand(0, uri, mContext)
+ underTest.share(0, uri, mContext)
bgExecutor.runAllReady()
verify(panelInteractor).collapsePanels()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index c9e958dd1cc0..d2bf9b888ef0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -50,9 +50,7 @@ import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.se
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.domain.resolver.homeSceneFamilyResolver
import com.android.systemui.scene.domain.startable.sceneContainerStartable
-import com.android.systemui.scene.shared.model.SceneFamilies
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.fakeSceneDataSource
import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
@@ -185,7 +183,6 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
fun swipeUpOnShadeScene_withAuthMethodSwipe_lockscreenNotDismissed_goesToLockscreen() =
testScope.runTest {
val actions by collectLastValue(kosmos.shadeUserActionsViewModel.actions)
- val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
kosmos.setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true)
kosmos.assertCurrentScene(Scenes.Lockscreen)
@@ -195,9 +192,8 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
val upDestinationSceneKey =
(actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene
- assertThat(upDestinationSceneKey).isEqualTo(SceneFamilies.Home)
- assertThat(homeScene).isEqualTo(Scenes.Lockscreen)
- kosmos.emulateUserDrivenTransition(to = homeScene)
+ assertThat(upDestinationSceneKey).isEqualTo(Scenes.Lockscreen)
+ kosmos.emulateUserDrivenTransition(to = Scenes.Lockscreen)
}
@Test
@@ -205,7 +201,6 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
testScope.runTest {
val actions by collectLastValue(kosmos.shadeUserActionsViewModel.actions)
val canSwipeToEnter by collectLastValue(kosmos.deviceEntryInteractor.canSwipeToEnter)
- val homeScene by collectLastValue(kosmos.homeSceneFamilyResolver.resolvedScene)
kosmos.setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true)
@@ -222,9 +217,8 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
val upDestinationSceneKey =
(actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene
- assertThat(upDestinationSceneKey).isEqualTo(SceneFamilies.Home)
- assertThat(homeScene).isEqualTo(Scenes.Gone)
- kosmos.emulateUserDrivenTransition(to = homeScene)
+ assertThat(upDestinationSceneKey).isEqualTo(Scenes.Gone)
+ kosmos.emulateUserDrivenTransition(to = Scenes.Gone)
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerHapticsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerHapticsViewModelTest.kt
new file mode 100644
index 000000000000..664315d19494
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerHapticsViewModelTest.kt
@@ -0,0 +1,233 @@
+/*
+ * 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.scene.ui.viewmodel
+
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.view.HapticFeedbackConstants
+import android.view.View
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ContentKey
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.ObservableTransitionState.Transition.ShowOrHideOverlay
+import com.android.compose.animation.scene.OverlayKey
+import com.android.compose.animation.scene.SceneKey
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.haptics.msdl.fakeMSDLPlayer
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.sceneContainerHapticsViewModelFactory
+import com.android.systemui.scene.shared.model.Overlays
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.testKosmos
+import com.google.android.msdl.data.model.MSDLToken
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.verifyZeroInteractions
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@EnableSceneContainer
+class SceneContainerHapticsViewModelTest() : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val sceneInteractor by lazy { kosmos.sceneInteractor }
+ private val msdlPlayer = kosmos.fakeMSDLPlayer
+ private val view = mock<View>()
+
+ private lateinit var underTest: SceneContainerHapticsViewModel
+
+ @Before
+ fun setup() {
+ underTest = kosmos.sceneContainerHapticsViewModelFactory.create(view)
+ underTest.activateIn(testScope)
+ }
+
+ @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+ @DisableFlags(Flags.FLAG_DUAL_SHADE)
+ @Test
+ fun onValidSceneTransition_withMSDL_playsMSDLShadePullHaptics() =
+ testScope.runTest {
+ // GIVEN a valid scene transition to play haptics
+ val validTransition = createTransitionState(from = Scenes.Gone, to = Scenes.Shade)
+
+ // WHEN the transition occurs
+ sceneInteractor.setTransitionState(MutableStateFlow(validTransition))
+ runCurrent()
+
+ // THEN the expected token plays without interaction properties
+ assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.SWIPE_THRESHOLD_INDICATOR)
+ assertThat(msdlPlayer.latestPropertiesPlayed).isNull()
+ }
+
+ @EnableFlags(Flags.FLAG_MSDL_FEEDBACK)
+ @DisableFlags(Flags.FLAG_DUAL_SHADE)
+ @Test
+ fun onInValidSceneTransition_withMSDL_doesNotPlayMSDLShadePullHaptics() =
+ testScope.runTest {
+ // GIVEN an invalid scene transition to play haptics
+ val invalidTransition = createTransitionState(from = Scenes.Shade, to = Scenes.Gone)
+
+ // WHEN the transition occurs
+ sceneInteractor.setTransitionState(MutableStateFlow(invalidTransition))
+ runCurrent()
+
+ // THEN the no token plays with no interaction properties
+ assertThat(msdlPlayer.latestTokenPlayed).isNull()
+ assertThat(msdlPlayer.latestPropertiesPlayed).isNull()
+ }
+
+ @DisableFlags(Flags.FLAG_DUAL_SHADE, Flags.FLAG_MSDL_FEEDBACK)
+ @Test
+ fun onValidSceneTransition_withoutMSDL_playsHapticConstantForShadePullHaptics() =
+ testScope.runTest {
+ // GIVEN a valid scene transition to play haptics
+ val validTransition = createTransitionState(from = Scenes.Gone, to = Scenes.Shade)
+
+ // WHEN the transition occurs
+ sceneInteractor.setTransitionState(MutableStateFlow(validTransition))
+ runCurrent()
+
+ // THEN the expected haptic feedback constant plays
+ verify(view).performHapticFeedback(eq(HapticFeedbackConstants.GESTURE_START))
+ }
+
+ @DisableFlags(Flags.FLAG_DUAL_SHADE, Flags.FLAG_MSDL_FEEDBACK)
+ @Test
+ fun onInValidSceneTransition_withoutMSDL_doesNotPlayHapticConstantForShadePullHaptics() =
+ testScope.runTest {
+ // GIVEN an invalid scene transition to play haptics
+ val invalidTransition = createTransitionState(from = Scenes.Shade, to = Scenes.Gone)
+
+ // WHEN the transition occurs
+ sceneInteractor.setTransitionState(MutableStateFlow(invalidTransition))
+ runCurrent()
+
+ // THEN the view does not play a haptic feedback constant
+ verifyZeroInteractions(view)
+ }
+
+ @EnableFlags(Flags.FLAG_MSDL_FEEDBACK, Flags.FLAG_DUAL_SHADE)
+ @Test
+ fun onValidOverlayTransition_withMSDL_playsMSDLShadePullHaptics() =
+ testScope.runTest {
+ // GIVEN a valid scene transition to play haptics
+ val validTransition =
+ createTransitionState(from = Scenes.Gone, to = Overlays.NotificationsShade)
+
+ // WHEN the transition occurs
+ sceneInteractor.setTransitionState(MutableStateFlow(validTransition))
+ runCurrent()
+
+ // THEN the expected token plays without interaction properties
+ assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.SWIPE_THRESHOLD_INDICATOR)
+ assertThat(msdlPlayer.latestPropertiesPlayed).isNull()
+ }
+
+ @EnableFlags(Flags.FLAG_MSDL_FEEDBACK, Flags.FLAG_DUAL_SHADE)
+ @Test
+ fun onInValidOverlayTransition_withMSDL_doesNotPlayMSDLShadePullHaptics() =
+ testScope.runTest {
+ // GIVEN an invalid scene transition to play haptics
+ val invalidTransition =
+ createTransitionState(from = Scenes.Bouncer, to = Overlays.NotificationsShade)
+
+ // WHEN the transition occurs
+ sceneInteractor.setTransitionState(MutableStateFlow(invalidTransition))
+ runCurrent()
+
+ // THEN the no token plays with no interaction properties
+ assertThat(msdlPlayer.latestTokenPlayed).isNull()
+ assertThat(msdlPlayer.latestPropertiesPlayed).isNull()
+ }
+
+ @EnableFlags(Flags.FLAG_DUAL_SHADE)
+ @DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
+ @Test
+ fun onValidOverlayTransition_withoutMSDL_playsHapticConstantForShadePullHaptics() =
+ testScope.runTest {
+ // GIVEN a valid scene transition to play haptics
+ val validTransition =
+ createTransitionState(from = Scenes.Gone, to = Overlays.NotificationsShade)
+
+ // WHEN the transition occurs
+ sceneInteractor.setTransitionState(MutableStateFlow(validTransition))
+ runCurrent()
+
+ // THEN the expected haptic feedback constant plays
+ verify(view).performHapticFeedback(eq(HapticFeedbackConstants.GESTURE_START))
+ }
+
+ @EnableFlags(Flags.FLAG_DUAL_SHADE)
+ @DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
+ @Test
+ fun onInValidOverlayTransition_withoutMSDL_doesNotPlayHapticConstantForShadePullHaptics() =
+ testScope.runTest {
+ // GIVEN an invalid scene transition to play haptics
+ val invalidTransition =
+ createTransitionState(from = Scenes.Bouncer, to = Overlays.NotificationsShade)
+
+ // WHEN the transition occurs
+ sceneInteractor.setTransitionState(MutableStateFlow(invalidTransition))
+ runCurrent()
+
+ // THEN the view does not play a haptic feedback constant
+ verifyZeroInteractions(view)
+ }
+
+ private fun createTransitionState(from: SceneKey, to: ContentKey) =
+ when (to) {
+ is SceneKey ->
+ ObservableTransitionState.Transition(
+ fromScene = from,
+ toScene = to,
+ currentScene = flowOf(from),
+ progress = MutableStateFlow(0.2f),
+ isInitiatedByUserInput = true,
+ isUserInputOngoing = flowOf(true),
+ )
+ is OverlayKey ->
+ ShowOrHideOverlay(
+ overlay = to,
+ fromContent = from,
+ toContent = to,
+ currentScene = from,
+ currentOverlays = sceneInteractor.currentOverlays,
+ progress = MutableStateFlow(0.2f),
+ isInitiatedByUserInput = true,
+ isUserInputOngoing = flowOf(true),
+ previewProgress = flowOf(0f),
+ isInPreviewStage = flowOf(false),
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
index e60e742e9917..a37f511cef69 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
@@ -21,23 +21,21 @@ package com.android.systemui.scene.ui.viewmodel
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.view.MotionEvent
+import android.view.View
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.DefaultEdgeDetector
import com.android.systemui.SysuiTestCase
-import com.android.systemui.classifier.domain.interactor.falsingInteractor
import com.android.systemui.classifier.fakeFalsingManager
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.kosmos.testScope
import com.android.systemui.lifecycle.activateIn
import com.android.systemui.power.data.repository.fakePowerRepository
-import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.fakeOverlaysByKeys
import com.android.systemui.scene.sceneContainerConfig
-import com.android.systemui.scene.sceneContainerGestureFilterFactory
-import com.android.systemui.scene.shared.logger.sceneLogger
+import com.android.systemui.scene.sceneContainerViewModelFactory
import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.fakeSceneDataSource
@@ -72,6 +70,7 @@ class SceneContainerViewModelTest : SysuiTestCase() {
private val fakeShadeRepository by lazy { kosmos.fakeShadeRepository }
private val sceneContainerConfig by lazy { kosmos.sceneContainerConfig }
private val falsingManager by lazy { kosmos.fakeFalsingManager }
+ private val view = mock<View>()
private lateinit var underTest: SceneContainerViewModel
@@ -81,16 +80,10 @@ class SceneContainerViewModelTest : SysuiTestCase() {
@Before
fun setUp() {
underTest =
- SceneContainerViewModel(
- sceneInteractor = sceneInteractor,
- falsingInteractor = kosmos.falsingInteractor,
- powerInteractor = kosmos.powerInteractor,
- shadeInteractor = kosmos.shadeInteractor,
- splitEdgeDetector = kosmos.splitEdgeDetector,
- logger = kosmos.sceneLogger,
- gestureFilterFactory = kosmos.sceneContainerGestureFilterFactory,
- displayId = kosmos.displayTracker.defaultDisplayId,
- motionEventHandlerReceiver = { motionEventHandler ->
+ kosmos.sceneContainerViewModelFactory.create(
+ view,
+ kosmos.displayTracker.defaultDisplayId,
+ { motionEventHandler ->
this@SceneContainerViewModelTest.motionEventHandler = motionEventHandler
},
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModelTest.kt
index 9f3e126ed1e8..15d68813808c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModelTest.kt
@@ -41,6 +41,7 @@ import com.android.systemui.qs.ui.adapter.fakeQSSceneAdapter
import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.domain.resolver.homeSceneFamilyResolver
+import com.android.systemui.scene.domain.startable.sceneContainerStartable
import com.android.systemui.scene.shared.model.SceneFamilies
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade
@@ -76,6 +77,7 @@ class ShadeUserActionsViewModelTest : SysuiTestCase() {
@Before
fun setUp() {
+ kosmos.sceneContainerStartable.start()
underTest.activateIn(testScope)
}
@@ -232,6 +234,20 @@ class ShadeUserActionsViewModelTest : SysuiTestCase() {
.isEmpty()
}
+ @Test
+ fun upTransitionSceneKey_backToCommunal() =
+ testScope.runTest {
+ val actions by collectLastValue(underTest.actions)
+ val currentScene by collectLastValue(kosmos.sceneInteractor.currentScene)
+ assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+ kosmos.sceneInteractor.changeScene(Scenes.Communal, "")
+ assertThat(currentScene).isEqualTo(Scenes.Communal)
+ kosmos.sceneInteractor.changeScene(Scenes.Shade, "")
+ assertThat(currentScene).isEqualTo(Scenes.Shade)
+
+ assertThat(actions?.get(Swipe.Up)).isEqualTo(UserActionResult(Scenes.Communal))
+ }
+
private fun TestScope.setDeviceEntered(isEntered: Boolean) {
if (isEntered) {
// Unlock the device marking the device has entered.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarSignalPolicyTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarSignalPolicyTest.kt
index 593f87382b52..173055364018 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarSignalPolicyTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarSignalPolicyTest.kt
@@ -45,7 +45,7 @@ import org.junit.Before
import org.junit.runner.RunWith
import org.mockito.Mockito.verify
import org.mockito.kotlin.clearInvocations
-import org.mockito.kotlin.verifyZeroInteractions
+import org.mockito.kotlin.verifyNoMoreInteractions
import kotlin.test.Test
@OptIn(ExperimentalCoroutinesApi::class)
@@ -112,11 +112,11 @@ class StatusBarSignalPolicyTest : SysuiTestCase() {
// flag is enabled.
underTest.setIsAirplaneMode(IconState(true, TelephonyIcons.FLIGHT_MODE_ICON, ""))
runCurrent()
- verifyZeroInteractions(statusBarIconController)
+ verifyNoMoreInteractions(statusBarIconController)
underTest.setIsAirplaneMode(IconState(false, TelephonyIcons.FLIGHT_MODE_ICON, ""))
runCurrent()
- verifyZeroInteractions(statusBarIconController)
+ verifyNoMoreInteractions(statusBarIconController)
}
@Test
@@ -126,7 +126,7 @@ class StatusBarSignalPolicyTest : SysuiTestCase() {
// Make sure StatusBarSignalPolicy.init does no initialization when
// the refactor flag is disabled.
underTest.init()
- verifyZeroInteractions(securityController, networkController, tunerService)
+ verifyNoMoreInteractions(securityController, networkController, tunerService)
}
@Test
@@ -154,11 +154,11 @@ class StatusBarSignalPolicyTest : SysuiTestCase() {
// if the StatusBarSignalPolicyRefactor is not enabled.
airplaneModeInteractor.setIsAirplaneMode(true)
runCurrent()
- verifyZeroInteractions(statusBarIconController)
+ verifyNoMoreInteractions(statusBarIconController)
airplaneModeInteractor.setIsAirplaneMode(false)
runCurrent()
- verifyZeroInteractions(statusBarIconController)
+ verifyNoMoreInteractions(statusBarIconController)
}
@Test
@@ -168,6 +168,6 @@ class StatusBarSignalPolicyTest : SysuiTestCase() {
// Make sure StatusBarSignalPolicy.start does no initialization when
// the refactor flag is disabled.
underTest.start()
- verifyZeroInteractions(securityController, networkController, tunerService)
+ verifyNoMoreInteractions(securityController, networkController, tunerService)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
index aefbc6b3b646..db274cc311dd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
@@ -19,6 +19,8 @@
package com.android.systemui.statusbar
import android.animation.ObjectAnimator
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.FlagsParameterization
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
@@ -47,8 +49,10 @@ import com.android.systemui.scene.data.repository.setTransition
import com.android.systemui.scene.domain.interactor.sceneBackInteractor
import com.android.systemui.scene.domain.interactor.sceneContainerOcclusionInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.shade.shared.flag.DualShade
import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
import com.android.systemui.testKosmos
import com.android.systemui.util.kotlin.JavaAdapter
@@ -79,6 +83,7 @@ class StatusBarStateControllerImplTest(flags: FlagsParameterization) : SysuiTest
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
+ private val sceneInteractor = kosmos.sceneInteractor
private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
private val mockDarkAnimator = mock<ObjectAnimator>()
@@ -229,14 +234,15 @@ class StatusBarStateControllerImplTest(flags: FlagsParameterization) : SysuiTest
@Test
fun testSetDreamState_getterReturnsCurrentState() {
underTest.setIsDreaming(true)
- assertTrue(underTest.isDreaming())
+ assertTrue(underTest.isDreaming)
underTest.setIsDreaming(false)
- assertFalse(underTest.isDreaming())
+ assertFalse(underTest.isDreaming)
}
@Test
@EnableSceneContainer
+ @DisableFlags(DualShade.FLAG_NAME)
fun start_hydratesStatusBarState_whileLocked() =
testScope.runTest {
var statusBarState = underTest.state
@@ -248,7 +254,7 @@ class StatusBarStateControllerImplTest(flags: FlagsParameterization) : SysuiTest
}
underTest.addCallback(listener)
- val currentScene by collectLastValue(kosmos.sceneInteractor.currentScene)
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
val deviceUnlockStatus by
collectLastValue(kosmos.deviceUnlockedInteractor.deviceUnlockStatus)
@@ -258,45 +264,107 @@ class StatusBarStateControllerImplTest(flags: FlagsParameterization) : SysuiTest
runCurrent()
assertThat(deviceUnlockStatus!!.isUnlocked).isFalse()
- kosmos.sceneInteractor.changeScene(
- toScene = Scenes.Lockscreen,
- loggingReason = "reason"
- )
+ sceneInteractor.changeScene(toScene = Scenes.Lockscreen, loggingReason = "reason")
runCurrent()
assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
// Call start to begin hydrating based on the scene framework:
underTest.start()
- kosmos.sceneInteractor.changeScene(toScene = Scenes.Bouncer, loggingReason = "reason")
+ sceneInteractor.changeScene(toScene = Scenes.Bouncer, loggingReason = "reason")
runCurrent()
assertThat(currentScene).isEqualTo(Scenes.Bouncer)
assertThat(statusBarState).isEqualTo(StatusBarState.KEYGUARD)
- kosmos.sceneInteractor.changeScene(toScene = Scenes.Shade, loggingReason = "reason")
+ sceneInteractor.changeScene(toScene = Scenes.Shade, loggingReason = "reason")
runCurrent()
assertThat(currentScene).isEqualTo(Scenes.Shade)
assertThat(statusBarState).isEqualTo(StatusBarState.SHADE_LOCKED)
- kosmos.sceneInteractor.changeScene(
- toScene = Scenes.QuickSettings,
- loggingReason = "reason"
- )
+ sceneInteractor.changeScene(toScene = Scenes.QuickSettings, loggingReason = "reason")
runCurrent()
assertThat(currentScene).isEqualTo(Scenes.QuickSettings)
assertThat(statusBarState).isEqualTo(StatusBarState.SHADE_LOCKED)
- kosmos.sceneInteractor.changeScene(toScene = Scenes.Communal, loggingReason = "reason")
+ sceneInteractor.changeScene(toScene = Scenes.Communal, loggingReason = "reason")
runCurrent()
assertThat(currentScene).isEqualTo(Scenes.Communal)
assertThat(statusBarState).isEqualTo(StatusBarState.KEYGUARD)
- kosmos.sceneInteractor.changeScene(
- toScene = Scenes.Lockscreen,
- loggingReason = "reason"
+ sceneInteractor.changeScene(toScene = Scenes.Lockscreen, loggingReason = "reason")
+ runCurrent()
+ assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+ assertThat(statusBarState).isEqualTo(StatusBarState.KEYGUARD)
+ }
+
+ @Test
+ @EnableSceneContainer
+ @EnableFlags(DualShade.FLAG_NAME)
+ fun start_hydratesStatusBarState_dualShade_whileLocked() =
+ testScope.runTest {
+ var statusBarState = underTest.state
+ val listener =
+ object : StatusBarStateController.StateListener {
+ override fun onStateChanged(newState: Int) {
+ statusBarState = newState
+ }
+ }
+ underTest.addCallback(listener)
+
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
+ val deviceUnlockStatus by
+ collectLastValue(kosmos.deviceUnlockedInteractor.deviceUnlockStatus)
+
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Password
)
runCurrent()
+ assertThat(deviceUnlockStatus!!.isUnlocked).isFalse()
+
+ sceneInteractor.changeScene(Scenes.Lockscreen, loggingReason = "reason")
+ runCurrent()
assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+
+ // Call start to begin hydrating based on the scene framework:
+ underTest.start()
+
+ sceneInteractor.changeScene(Scenes.Bouncer, loggingReason = "reason")
+ runCurrent()
+ assertThat(currentScene).isEqualTo(Scenes.Bouncer)
+ assertThat(currentOverlays).isEmpty()
+ assertThat(statusBarState).isEqualTo(StatusBarState.KEYGUARD)
+
+ sceneInteractor.changeScene(Scenes.Lockscreen, loggingReason = "reason")
+ runCurrent()
+
+ sceneInteractor.showOverlay(Overlays.NotificationsShade, loggingReason = "reason")
+ runCurrent()
+ assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+ assertThat(currentOverlays).containsExactly(Overlays.NotificationsShade)
+ assertThat(statusBarState).isEqualTo(StatusBarState.SHADE_LOCKED)
+
+ sceneInteractor.replaceOverlay(
+ from = Overlays.NotificationsShade,
+ to = Overlays.QuickSettingsShade,
+ loggingReason = "reason",
+ )
+ runCurrent()
+ assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+ assertThat(currentOverlays).containsExactly(Overlays.QuickSettingsShade)
+ assertThat(statusBarState).isEqualTo(StatusBarState.SHADE_LOCKED)
+
+ sceneInteractor.hideOverlay(Overlays.QuickSettingsShade, loggingReason = "reason")
+ sceneInteractor.changeScene(Scenes.Communal, loggingReason = "reason")
+ runCurrent()
+ assertThat(currentScene).isEqualTo(Scenes.Communal)
+ assertThat(currentOverlays).isEmpty()
+ assertThat(statusBarState).isEqualTo(StatusBarState.KEYGUARD)
+
+ sceneInteractor.changeScene(Scenes.Lockscreen, loggingReason = "reason")
+ runCurrent()
+ assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+ assertThat(currentOverlays).isEmpty()
assertThat(statusBarState).isEqualTo(StatusBarState.KEYGUARD)
}
@@ -313,7 +381,7 @@ class StatusBarStateControllerImplTest(flags: FlagsParameterization) : SysuiTest
}
underTest.addCallback(listener)
- val currentScene by collectLastValue(kosmos.sceneInteractor.currentScene)
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
val deviceUnlockStatus by
collectLastValue(kosmos.deviceUnlockedInteractor.deviceUnlockStatus)
kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
@@ -326,10 +394,7 @@ class StatusBarStateControllerImplTest(flags: FlagsParameterization) : SysuiTest
assertThat(deviceUnlockStatus!!.isUnlocked).isTrue()
- kosmos.sceneInteractor.changeScene(
- toScene = Scenes.Lockscreen,
- loggingReason = "reason"
- )
+ sceneInteractor.changeScene(toScene = Scenes.Lockscreen, loggingReason = "reason")
runCurrent()
assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
@@ -339,20 +404,17 @@ class StatusBarStateControllerImplTest(flags: FlagsParameterization) : SysuiTest
assertThat(statusBarState).isEqualTo(StatusBarState.KEYGUARD)
- kosmos.sceneInteractor.changeScene(toScene = Scenes.Gone, loggingReason = "reason")
+ sceneInteractor.changeScene(toScene = Scenes.Gone, loggingReason = "reason")
runCurrent()
assertThat(currentScene).isEqualTo(Scenes.Gone)
assertThat(statusBarState).isEqualTo(StatusBarState.SHADE)
- kosmos.sceneInteractor.changeScene(toScene = Scenes.Shade, loggingReason = "reason")
+ sceneInteractor.changeScene(toScene = Scenes.Shade, loggingReason = "reason")
runCurrent()
assertThat(currentScene).isEqualTo(Scenes.Shade)
assertThat(statusBarState).isEqualTo(StatusBarState.SHADE)
- kosmos.sceneInteractor.changeScene(
- toScene = Scenes.QuickSettings,
- loggingReason = "reason"
- )
+ sceneInteractor.changeScene(toScene = Scenes.QuickSettings, loggingReason = "reason")
runCurrent()
assertThat(currentScene).isEqualTo(Scenes.QuickSettings)
assertThat(statusBarState).isEqualTo(StatusBarState.SHADE)
@@ -371,7 +433,7 @@ class StatusBarStateControllerImplTest(flags: FlagsParameterization) : SysuiTest
}
underTest.addCallback(listener)
- val currentScene by collectLastValue(kosmos.sceneInteractor.currentScene)
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
val isOccluded by
collectLastValue(kosmos.sceneContainerOcclusionInteractor.invisibleDueToOcclusion)
@@ -385,15 +447,12 @@ class StatusBarStateControllerImplTest(flags: FlagsParameterization) : SysuiTest
// Call start to begin hydrating based on the scene framework:
underTest.start()
- kosmos.sceneInteractor.changeScene(toScene = Scenes.Shade, loggingReason = "reason")
+ sceneInteractor.changeScene(toScene = Scenes.Shade, loggingReason = "reason")
runCurrent()
assertThat(currentScene).isEqualTo(Scenes.Shade)
assertThat(statusBarState).isEqualTo(StatusBarState.SHADE)
- kosmos.sceneInteractor.changeScene(
- toScene = Scenes.QuickSettings,
- loggingReason = "reason"
- )
+ sceneInteractor.changeScene(toScene = Scenes.QuickSettings, loggingReason = "reason")
runCurrent()
assertThat(currentScene).isEqualTo(Scenes.QuickSettings)
assertThat(statusBarState).isEqualTo(StatusBarState.SHADE)
@@ -415,10 +474,7 @@ class StatusBarStateControllerImplTest(flags: FlagsParameterization) : SysuiTest
kosmos.setTransition(
sceneTransition = Idle(Scenes.Gone),
stateTransition =
- TransitionStep(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.GONE,
- )
+ TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE),
)
assertThat(underTest.leaveOpenOnKeyguardHide()).isEqualTo(false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt
index 2ad3c9e1c21a..22906b8724b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt
@@ -40,7 +40,7 @@ import org.mockito.Mockito.anyInt
import org.mockito.Mockito.anyString
import org.mockito.Mockito.eq
import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.Mockito.`when` as whenever
private const val SDK_VERSION = 33
@@ -80,7 +80,7 @@ class TargetSdkResolverTest : SysuiTestCase() {
notifListener.onEntryBind(entry, sbn)
assertEquals(SDK_VERSION, entry.targetSdk)
- verifyZeroInteractions(packageManager)
+ verifyNoMoreInteractions(packageManager)
}
@Test
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 7b87aeb60c13..d772e3effbeb 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
@@ -42,7 +42,8 @@ import com.android.systemui.statusbar.notification.collection.modifyEntry
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRowRepository
import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
-import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype
+import com.android.systemui.statusbar.notification.domain.interactor.lockScreenNotificationMinimalismSetting
+import com.android.systemui.statusbar.notification.shared.NotificationMinimalism
import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository
import com.android.systemui.testKosmos
import com.android.systemui.util.settings.FakeSettings
@@ -66,7 +67,7 @@ import org.mockito.kotlin.whenever
@SmallTest
@RunWith(AndroidJUnit4::class)
-@EnableFlags(NotificationMinimalismPrototype.FLAG_NAME)
+@EnableFlags(NotificationMinimalism.FLAG_NAME)
class LockScreenMinimalismCoordinatorTest : SysuiTestCase() {
private val kosmos =
@@ -76,7 +77,7 @@ class LockScreenMinimalismCoordinatorTest : SysuiTestCase() {
mock<SysuiStatusBarStateController>().also { mock ->
doAnswer { statusBarState.ordinal }.whenever(mock).state
}
- fakeSettings.putInt(Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, 1)
+ lockScreenNotificationMinimalismSetting = true
}
private val notifPipeline: NotifPipeline = mock()
private var statusBarState: StatusBarState = StatusBarState.KEYGUARD
@@ -193,7 +194,7 @@ class LockScreenMinimalismCoordinatorTest : SysuiTestCase() {
kosmos.activeNotificationListRepository.topUnseenNotificationKey.value = child2.key
assertThat(promoter.shouldPromoteToTopLevel(child1)).isFalse()
assertThat(promoter.shouldPromoteToTopLevel(child2))
- .isEqualTo(NotificationMinimalismPrototype.ungroupTopUnseen)
+ .isEqualTo(NotificationMinimalism.ungroupTopUnseen)
assertThat(promoter.shouldPromoteToTopLevel(child3)).isFalse()
assertThat(promoter.shouldPromoteToTopLevel(parent)).isFalse()
@@ -201,7 +202,7 @@ class LockScreenMinimalismCoordinatorTest : SysuiTestCase() {
kosmos.activeNotificationListRepository.topUnseenNotificationKey.value = child2.key
assertThat(promoter.shouldPromoteToTopLevel(child1)).isTrue()
assertThat(promoter.shouldPromoteToTopLevel(child2))
- .isEqualTo(NotificationMinimalismPrototype.ungroupTopUnseen)
+ .isEqualTo(NotificationMinimalism.ungroupTopUnseen)
assertThat(promoter.shouldPromoteToTopLevel(child3)).isFalse()
assertThat(promoter.shouldPromoteToTopLevel(parent)).isFalse()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorTest.kt
index 2159b864d2a2..ea2e25e8eb1c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorTest.kt
@@ -21,7 +21,7 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
-import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype
+import com.android.systemui.statusbar.notification.shared.NotificationMinimalism
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
@@ -57,7 +57,7 @@ class SeenNotificationsInteractorTest : SysuiTestCase() {
}
@Test
- @EnableFlags(NotificationMinimalismPrototype.FLAG_NAME)
+ @EnableFlags(NotificationMinimalism.FLAG_NAME)
fun topOngoingAndUnseenNotification() = runTest {
val entry1 = NotificationEntryBuilder().setTag("entry1").build()
val entry2 = NotificationEntryBuilder().setTag("entry2").build()
@@ -91,4 +91,17 @@ class SeenNotificationsInteractorTest : SysuiTestCase() {
testScheduler.runCurrent()
assertThat(settingEnabled).isTrue()
}
+
+ fun testLockScreenNotificationMinimalismSetting() = runTest {
+ val settingEnabled by
+ collectLastValue(underTest.isLockScreenNotificationMinimalismEnabled())
+
+ kosmos.lockScreenNotificationMinimalismSetting = false
+ testScheduler.runCurrent()
+ assertThat(settingEnabled).isFalse()
+
+ kosmos.lockScreenNotificationMinimalismSetting = true
+ testScheduler.runCurrent()
+ assertThat(settingEnabled).isTrue()
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModelTest.kt
index f9b77697b767..28857a08c2bd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModelTest.kt
@@ -30,6 +30,7 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.andSceneContainer
import com.android.systemui.kosmos.testScope
+import com.android.systemui.shared.settings.data.repository.fakeSecureSettingsRepository
import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
@@ -53,6 +54,7 @@ class EmptyShadeViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
private val testScope = kosmos.testScope
private val zenModeRepository = kosmos.zenModeRepository
private val activeNotificationListRepository = kosmos.activeNotificationListRepository
+ private val fakeSecureSettingsRepository = kosmos.fakeSecureSettingsRepository
private val underTest = kosmos.emptyShadeViewModel
@@ -205,4 +207,84 @@ class EmptyShadeViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
assertThat(footerVisible).isTrue()
}
+
+ @EnableFlags(ModesEmptyShadeFix.FLAG_NAME)
+ @Test
+ fun onClick_whenHistoryDisabled_leadsToSettingsPage() =
+ testScope.runTest {
+ val onClick by collectLastValue(underTest.onClick)
+ runCurrent()
+
+ fakeSecureSettingsRepository.setInt(Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0)
+
+ assertThat(onClick?.targetIntent?.action)
+ .isEqualTo(Settings.ACTION_NOTIFICATION_SETTINGS)
+ assertThat(onClick?.backStack).isEmpty()
+ }
+
+ @EnableFlags(ModesEmptyShadeFix.FLAG_NAME)
+ @Test
+ fun onClick_whenHistoryEnabled_leadsToHistoryPage() =
+ testScope.runTest {
+ val onClick by collectLastValue(underTest.onClick)
+ runCurrent()
+
+ fakeSecureSettingsRepository.setInt(Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 1)
+
+ assertThat(onClick?.targetIntent?.action)
+ .isEqualTo(Settings.ACTION_NOTIFICATION_HISTORY)
+ assertThat(onClick?.backStack?.map { it.action })
+ .containsExactly(Settings.ACTION_NOTIFICATION_SETTINGS)
+ }
+
+ @EnableFlags(ModesEmptyShadeFix.FLAG_NAME)
+ @Test
+ fun onClick_whenOneModeHidingNotifications_leadsToModeSettings() =
+ testScope.runTest {
+ val onClick by collectLastValue(underTest.onClick)
+ runCurrent()
+
+ zenModeRepository.addMode(
+ TestModeBuilder()
+ .setId("ID")
+ .setActive(true)
+ .setVisualEffect(VISUAL_EFFECT_NOTIFICATION_LIST, /* allowed= */ false)
+ .build()
+ )
+ runCurrent()
+
+ assertThat(onClick?.targetIntent?.action)
+ .isEqualTo(Settings.ACTION_AUTOMATIC_ZEN_RULE_SETTINGS)
+ assertThat(
+ onClick?.targetIntent?.extras?.getString(Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID)
+ )
+ .isEqualTo("ID")
+ assertThat(onClick?.backStack?.map { it.action })
+ .containsExactly(Settings.ACTION_ZEN_MODE_SETTINGS)
+ }
+
+ @EnableFlags(ModesEmptyShadeFix.FLAG_NAME)
+ @Test
+ fun onClick_whenMultipleModesHidingNotifications_leadsToGeneralModesSettings() =
+ testScope.runTest {
+ val onClick by collectLastValue(underTest.onClick)
+ runCurrent()
+
+ zenModeRepository.addMode(
+ TestModeBuilder()
+ .setActive(true)
+ .setVisualEffect(VISUAL_EFFECT_NOTIFICATION_LIST, /* allowed= */ false)
+ .build()
+ )
+ zenModeRepository.addMode(
+ TestModeBuilder()
+ .setActive(true)
+ .setVisualEffect(VISUAL_EFFECT_NOTIFICATION_LIST, /* allowed= */ false)
+ .build()
+ )
+ runCurrent()
+
+ assertThat(onClick?.targetIntent?.action).isEqualTo(Settings.ACTION_ZEN_MODE_SETTINGS)
+ assertThat(onClick?.backStack).isEmpty()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt
index fda5cd286074..da488336c7c3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt
@@ -42,7 +42,7 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.verifyNoMoreInteractions
private const val FREE_IMAGE_DELAY_MS = 4000L
private const val MAX_IMAGE_SIZE = 512 // size of the test drawables in pixels
@@ -160,7 +160,7 @@ class BigPictureIconManagerTest : SysuiTestCase() {
assertIsPlaceHolder(drawableCaptor.value)
assertSize(drawableCaptor.value)
// AND nothing happens on the old consumer
- verifyZeroInteractions(mockConsumer)
+ verifyNoMoreInteractions(mockConsumer)
}
@Test
@@ -182,7 +182,7 @@ class BigPictureIconManagerTest : SysuiTestCase() {
assertIsFullImage(drawableCaptor.value)
assertSize(drawableCaptor.value)
// AND nothing happens on the old consumer
- verifyZeroInteractions(mockConsumer)
+ verifyNoMoreInteractions(mockConsumer)
}
@Test
@@ -332,7 +332,7 @@ class BigPictureIconManagerTest : SysuiTestCase() {
runCurrent()
// THEN nothing happens
- verifyZeroInteractions(mockConsumer)
+ verifyNoMoreInteractions(mockConsumer)
}
@Test
@@ -347,7 +347,7 @@ class BigPictureIconManagerTest : SysuiTestCase() {
runCurrent()
// THEN nothing happens
- verifyZeroInteractions(mockConsumer)
+ verifyNoMoreInteractions(mockConsumer)
}
@Test
@@ -362,7 +362,7 @@ class BigPictureIconManagerTest : SysuiTestCase() {
runCurrent()
// THEN nothing happens
- verifyZeroInteractions(mockConsumer)
+ verifyNoMoreInteractions(mockConsumer)
}
@Test
@@ -382,7 +382,7 @@ class BigPictureIconManagerTest : SysuiTestCase() {
runCurrent()
// THEN nothing happens
- verifyZeroInteractions(mockConsumer)
+ verifyNoMoreInteractions(mockConsumer)
}
@Test
@@ -399,7 +399,7 @@ class BigPictureIconManagerTest : SysuiTestCase() {
runCurrent()
// THEN nothing happens
- verifyZeroInteractions(mockConsumer)
+ verifyNoMoreInteractions(mockConsumer)
}
@Test
@@ -416,7 +416,7 @@ class BigPictureIconManagerTest : SysuiTestCase() {
runCurrent()
// THEN nothing happens
- verifyZeroInteractions(mockConsumer)
+ verifyNoMoreInteractions(mockConsumer)
}
@Test
@@ -436,7 +436,7 @@ class BigPictureIconManagerTest : SysuiTestCase() {
runCurrent()
// THEN nothing happens
- verifyZeroInteractions(mockConsumer)
+ verifyNoMoreInteractions(mockConsumer)
}
@Test
@@ -457,7 +457,7 @@ class BigPictureIconManagerTest : SysuiTestCase() {
runCurrent()
// THEN no more updates are happening
- verifyZeroInteractions(mockConsumer)
+ verifyNoMoreInteractions(mockConsumer)
}
private fun overrideMaxImageSizes() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSettingsControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationSettingsControllerTest.kt
index 310fa67a3c6b..1c7a8361980f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSettingsControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationSettingsControllerTest.kt
@@ -50,7 +50,6 @@ import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
-import org.mockito.Mockito.verifyZeroInteractions
import org.mockito.MockitoAnnotations
@SmallTest
@@ -139,7 +138,7 @@ class NotificationSettingsControllerTest : SysuiTestCase() {
@Test
fun addCallback_onlyFirstForUriRegistersObserver() {
controller.addCallback(settingUri1, Mockito.mock(Listener::class.java))
- verifyZeroInteractions(secureSettings)
+ verifyNoMoreInteractions(secureSettings)
testableLooper.processAllMessages()
verify(secureSettings)
.registerContentObserverForUserSync(
@@ -157,7 +156,7 @@ class NotificationSettingsControllerTest : SysuiTestCase() {
@Test
fun addCallback_secondUriRegistersObserver() {
controller.addCallback(settingUri1, Mockito.mock(Listener::class.java))
- verifyZeroInteractions(secureSettings)
+ verifyNoMoreInteractions(secureSettings)
testableLooper.processAllMessages()
verify(secureSettings)
.registerContentObserverForUserSync(
@@ -185,7 +184,7 @@ class NotificationSettingsControllerTest : SysuiTestCase() {
val listenerSetting1: Listener = mock()
val listenerSetting2: Listener = mock()
controller.addCallback(settingUri1, listenerSetting1)
- verifyZeroInteractions(secureSettings)
+ verifyNoMoreInteractions(secureSettings)
testableLooper.processAllMessages()
verify(secureSettings)
.registerContentObserverForUserSync(
@@ -229,7 +228,7 @@ class NotificationSettingsControllerTest : SysuiTestCase() {
controller.addCallback(settingUri1, listenerSetting1b)
controller.addCallback(settingUri2, listenerSetting2)
- verifyZeroInteractions(secureSettings)
+ verifyNoMoreInteractions(secureSettings)
testableLooper.processAllMessages()
verify(listenerSetting1a)
@@ -251,7 +250,7 @@ class NotificationSettingsControllerTest : SysuiTestCase() {
// First, register
controller.addCallback(settingUri1, listenerSetting1a)
controller.addCallback(settingUri1, listenerSetting1b)
- verifyZeroInteractions(secureSettings)
+ verifyNoMoreInteractions(secureSettings)
testableLooper.processAllMessages()
verify(secureSettings)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLoggerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLoggerTest.kt
index c61756c42895..13280f2caec7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLoggerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationStatsLoggerTest.kt
@@ -40,7 +40,7 @@ import org.junit.runner.RunWith
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.verifyNoMoreInteractions
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -115,7 +115,7 @@ class NotificationStatsLoggerTest : SysuiTestCase() {
// THEN visibility changes are reported
verify(mockStatusBarService)
.onNotificationVisibilityChanged(eq(emptyArray()), visibilityArrayCaptor.capture())
- verifyZeroInteractions(mockNotificationListenerService)
+ verifyNoMoreInteractions(mockNotificationListenerService)
val noLongerVisible = visibilityArrayCaptor.value
assertThat(noLongerVisible).hasLength(2)
assertThat(noLongerVisible[0]).apply {
@@ -152,7 +152,7 @@ class NotificationStatsLoggerTest : SysuiTestCase() {
// THEN visibility changes are reported
verify(mockStatusBarService)
.onNotificationVisibilityChanged(eq(emptyArray()), visibilityArrayCaptor.capture())
- verifyZeroInteractions(mockNotificationListenerService)
+ verifyNoMoreInteractions(mockNotificationListenerService)
val noLongerVisible = visibilityArrayCaptor.value
assertThat(noLongerVisible).hasLength(2)
assertThat(noLongerVisible[0]).apply {
@@ -186,7 +186,7 @@ class NotificationStatsLoggerTest : SysuiTestCase() {
runCurrent()
// THEN no visibility changes are reported
- verifyZeroInteractions(mockStatusBarService, mockNotificationListenerService)
+ verifyNoMoreInteractions(mockStatusBarService, mockNotificationListenerService)
}
@Test
@@ -205,7 +205,7 @@ class NotificationStatsLoggerTest : SysuiTestCase() {
runCurrent()
// THEN we call the new Callable
- verifyZeroInteractions(callable)
+ verifyNoMoreInteractions(callable)
verify(otherCallable).call()
}
@@ -226,7 +226,7 @@ class NotificationStatsLoggerTest : SysuiTestCase() {
// THEN visibility changes are reported
verify(mockStatusBarService)
.onNotificationVisibilityChanged(eq(emptyArray()), visibilityArrayCaptor.capture())
- verifyZeroInteractions(mockNotificationListenerService)
+ verifyNoMoreInteractions(mockNotificationListenerService)
val noLongerVisible = visibilityArrayCaptor.value
assertThat(noLongerVisible).hasLength(2)
assertThat(noLongerVisible[0]).apply {
@@ -325,7 +325,7 @@ class NotificationStatsLoggerTest : SysuiTestCase() {
runCurrent()
// THEN the Expand event is not reported again
- verifyZeroInteractions(mockStatusBarService)
+ verifyNoMoreInteractions(mockStatusBarService)
}
@Test
@@ -341,7 +341,7 @@ class NotificationStatsLoggerTest : SysuiTestCase() {
runCurrent()
// No events are reported
- verifyZeroInteractions(mockStatusBarService)
+ verifyNoMoreInteractions(mockStatusBarService)
}
@Test
@@ -424,7 +424,7 @@ class NotificationStatsLoggerTest : SysuiTestCase() {
// THEN no events are reported, because we consider the Notification initially
// collapsed, so only expanded is logged in the first time.
- verifyZeroInteractions(mockStatusBarService)
+ verifyNoMoreInteractions(mockStatusBarService)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerFlagDisabledTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerFlagDisabledTest.kt
index 81f095041fbc..799e957656dc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerFlagDisabledTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerFlagDisabledTest.kt
@@ -34,7 +34,7 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
-import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.MockitoAnnotations
@SmallTest
@@ -70,6 +70,6 @@ class SensitiveNotificationProtectionControllerFlagDisabledTest : SysuiTestCase(
@Test
fun init_noRegisterMediaProjectionManagerCallback() {
- verifyZeroInteractions(mediaProjectionManager)
+ verifyNoMoreInteractions(mediaProjectionManager)
}
}
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 be44dee0aae6..73626b457dcf 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
@@ -184,7 +184,10 @@ public interface QSTile {
}
}
- /** Get the text for secondaryLabel. */
+ /**
+ * If the current secondaryLabel value is not empty, ignore the given input and return
+ * the current value. Otherwise return current value.
+ */
public CharSequence getSecondaryLabel(CharSequence stateText) {
// Use a local reference as the value might change from other threads
CharSequence localSecondaryLabel = secondaryLabel;
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index d7c13598644f..a9f9fafd505f 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"Gesprekke"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Vee alle stil kennisgewings uit"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Kennisgewings onderbreek deur Moenie Steur Nie"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"Begin nou"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"Geen kennisgewings nie"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"Geen nuwe kennisgewings nie"</string>
@@ -1179,10 +1181,8 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Luidsprekers en skerms"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Voorgestelde toestelle"</string>
- <!-- no translation found for media_input_group_title (2057057473860783021) -->
- <skip />
- <!-- no translation found for media_output_group_title (6789001895863332576) -->
- <skip />
+ <string name="media_input_group_title" msgid="2057057473860783021">"Invoer"</string>
+ <string name="media_output_group_title" msgid="6789001895863332576">"Uitvoer"</string>
<string name="media_output_end_session_dialog_summary" msgid="5954520685989877347">"Stop jou gedeelde sessie om media na ’n ander toestel toe te skuif"</string>
<string name="media_output_end_session_dialog_stop" msgid="208189434474624412">"Stop"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Hoe uitsaai werk"</string>
@@ -1398,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Vou ikoon in"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Vou ikoon uit"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"of"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Sleephandvatsel"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Navigeer met jou sleutelbord"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Leer kortpadsleutels"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Navigeer met jou raakpaneel"</string>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index 388601bc157d..704bdcaa108a 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"ውይይቶች"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"ሁሉንም ጸጥ ያሉ ማሳወቂያዎችን ያጽዱ"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"ማሳወቂያዎች በአትረብሽ ባሉበት ቆመዋል"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"አሁን ጀምር"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"ምንም ማሳወቂያ የለም"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"ምንም አዲስ ማሳወቂያዎች የሉም"</string>
@@ -1396,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"መሰብሰቢያ አዶ"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"መዘርጊያ አዶ"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ወይም"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"መያዣ ይጎትቱ"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"የቁልፍ ሰሌዳዎን በመጠቀም ያስሱ"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"የቁልፍ ሰሌዳ አቋራጮችን ይወቁ"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"የመዳሰሻ ሰሌዳዎን በመጠቀም ያስሱ"</string>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index 53b1dfb920fa..8ed225df457b 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"المحادثات"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"محو جميع الإشعارات الصامتة"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"تم إيقاف الإشعارات مؤقتًا وفقًا لإعداد \"عدم الإزعاج\""</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"البدء الآن"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"ما مِن إشعارات"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"ما مِن إشعارات جديدة"</string>
@@ -772,7 +774,7 @@
<string name="feedback_prompt" msgid="3656728972307896379">"أخبِر مطوِّر البرامج برأيك. هل كان هذا صحيحًا؟"</string>
<string name="notification_channel_controls_opened_accessibility" msgid="6111817750774381094">"تم فتح عناصر التحكم في الإشعارات لتطبيق <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="notification_channel_controls_closed_accessibility" msgid="1561909368876911701">"تم إغلاق عناصر التحكم في الإشعارات لتطبيق <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
- <string name="notification_more_settings" msgid="4936228656989201793">"المزيد من الإعدادات"</string>
+ <string name="notification_more_settings" msgid="4936228656989201793">"إعدادات إضافية"</string>
<string name="notification_app_settings" msgid="8963648463858039377">"تخصيص"</string>
<string name="notification_conversation_bubble" msgid="2242180995373949022">"إظهار فقاعة تفسيرية"</string>
<string name="notification_conversation_unbubble" msgid="6908427185031099868">"إزالة فقاعات المحادثات"</string>
@@ -1396,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"رمز التصغير"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"رمز التوسيع"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"أو"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"مقبض السحب"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"التنقّل باستخدام لوحة المفاتيح"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"تعرَّف على اختصارات لوحة المفاتيح"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"التنقّل باستخدام لوحة اللمس"</string>
diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml
index 04cb99cb7157..07c6f73c8975 100644
--- a/packages/SystemUI/res/values-as/strings.xml
+++ b/packages/SystemUI/res/values-as/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"বাৰ্তালাপ"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"আটাইবোৰ নীৰৱ জাননী মচক"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"অসুবিধা নিদিব-ই জাননী পজ কৰিছে"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"এতিয়াই আৰম্ভ কৰক"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"কোনো জাননী নাই"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"কোনো নতুন জাননী নাই"</string>
@@ -1396,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"সংকোচন কৰাৰ চিহ্ন"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"বিস্তাৰ কৰাৰ চিহ্ন"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"অথবা"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"ড্ৰেগ হেণ্ডেল"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"কীব’ৰ্ড ব্যৱহাৰ কৰি নেভিগে’ট কৰক"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"কীব’ৰ্ডৰ শ্বৰ্টকাটসমূহৰ বিষয়ে জানক"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"আপোনাৰ টাচ্চপেড ব্যৱহাৰ কৰি নেভিগে’ট কৰক"</string>
diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml
index 85aa42f13a23..551ec5cd436b 100644
--- a/packages/SystemUI/res/values-az/strings.xml
+++ b/packages/SystemUI/res/values-az/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"Söhbətlər"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Səssiz bildirişlərin hamısını silin"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Bildirişlər \"Narahat Etməyin\" rejimi tərəfindən dayandırıldı"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"İndi başlayın"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"Heç bir bildiriş yoxdur"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"Yeni bildiriş yoxdur"</string>
@@ -1179,10 +1181,8 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Dinamiklər &amp; Displeylər"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Təklif olunan Cihazlar"</string>
- <!-- no translation found for media_input_group_title (2057057473860783021) -->
- <skip />
- <!-- no translation found for media_output_group_title (6789001895863332576) -->
- <skip />
+ <string name="media_input_group_title" msgid="2057057473860783021">"Daxiletmə"</string>
+ <string name="media_output_group_title" msgid="6789001895863332576">"Nəticə"</string>
<string name="media_output_end_session_dialog_summary" msgid="5954520685989877347">"Medianı başqa cihaza köçürmək üçün paylaşılan sessiyanı dayandırın"</string>
<string name="media_output_end_session_dialog_stop" msgid="208189434474624412">"Dayandırın"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Yayım necə işləyir"</string>
@@ -1398,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"İkonanı yığcamlaşdırın"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"İkonanı genişləndirin"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"və ya"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Dəstəyi çəkin"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Klaviaturadan istifadə edərək hərəkət edin"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Klaviatura qısayolları haqqında öyrənin"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Taçpeddən istifadə edərək hərəkət edin"</string>
diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
index b838f7c445b5..20dee4603e7b 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"Konverzacije"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Obrišite sva nečujna obaveštenja"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Obaveštenja su pauzirana režimom Ne uznemiravaj"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"Započni"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"Nema obaveštenja"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"Nema novih obaveštenja"</string>
@@ -1396,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ikona za skupljanje"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ikona za proširivanje"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ili"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Marker za prevlačenje"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Krećite se pomoću tastature"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Saznajte više o tasterskim prečicama"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Krećite se pomoću tačpeda"</string>
diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml
index 038732c45559..ba0a8b422474 100644
--- a/packages/SystemUI/res/values-be/strings.xml
+++ b/packages/SystemUI/res/values-be/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"Размовы"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Выдаліць усе апавяшчэнні без гуку"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Паказ апавяшчэнняў прыпынены ў рэжыме \"Не турбаваць\""</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"Пачаць зараз"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"Апавяшчэнняў няма"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"Няма новых апавяшчэнняў"</string>
@@ -1179,10 +1181,8 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Дынамікі і дысплэі"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Прылады, якія падтрымліваюцца"</string>
- <!-- no translation found for media_input_group_title (2057057473860783021) -->
- <skip />
- <!-- no translation found for media_output_group_title (6789001895863332576) -->
- <skip />
+ <string name="media_input_group_title" msgid="2057057473860783021">"Увод"</string>
+ <string name="media_output_group_title" msgid="6789001895863332576">"Вывад"</string>
<string name="media_output_end_session_dialog_summary" msgid="5954520685989877347">"Каб перамясціць медыяфайлы на іншую прыладу, спыніце абагулены сеанс"</string>
<string name="media_output_end_session_dialog_stop" msgid="208189434474624412">"Спыніць"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Як адбываецца трансляцыя"</string>
@@ -1398,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Значок \"Згарнуць\""</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Значок \"Разгарнуць\""</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"або"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Маркер перацягвання"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Навігацыя з дапамогай клавіятуры"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Азнаёмцеся са спалучэннямі клавіш"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Навігацыя з дапамогай сэнсарнай панэлі"</string>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index 17a8ed520618..bf558eb42708 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"Разговори"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Изчистване на всички беззвучни известия"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Известията са поставени на пауза от режима „Не безпокойте“"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"Стартиране сега"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"Няма известия"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"Няма нови известия"</string>
@@ -1179,10 +1181,8 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Високоговорители и екрани"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Предложени устройства"</string>
- <!-- no translation found for media_input_group_title (2057057473860783021) -->
- <skip />
- <!-- no translation found for media_output_group_title (6789001895863332576) -->
- <skip />
+ <string name="media_input_group_title" msgid="2057057473860783021">"Вход"</string>
+ <string name="media_output_group_title" msgid="6789001895863332576">"Изход"</string>
<string name="media_output_end_session_dialog_summary" msgid="5954520685989877347">"Спиране на споделената ви сесия с цел преместване на мултимедията на друго устройство"</string>
<string name="media_output_end_session_dialog_stop" msgid="208189434474624412">"Спиране"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Как работи предаването"</string>
@@ -1398,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Икона за свиване"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Икона за разгъване"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"или"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Манипулатор за преместване с плъзгане"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Навигирайте посредством клавиатурата си"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Научете за клавишните комбинации"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Навигирайте посредством сензорния панел"</string>
diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml
index ebec6522d049..cf55feb343e8 100644
--- a/packages/SystemUI/res/values-bn/strings.xml
+++ b/packages/SystemUI/res/values-bn/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"কথোপকথন"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"সব নীরব বিজ্ঞপ্তি মুছুন"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"\'বিরক্ত করবে না\' দিয়ে বিজ্ঞপ্তি পজ করা হয়েছে"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"এখন শুরু করুন"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"কোনও বিজ্ঞপ্তি নেই"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"নতুন কোনও বিজ্ঞপ্তি নেই"</string>
@@ -1179,10 +1181,8 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"স্পিকার ও ডিসপ্লে"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"সাজেস্ট করা ডিভাইস"</string>
- <!-- no translation found for media_input_group_title (2057057473860783021) -->
- <skip />
- <!-- no translation found for media_output_group_title (6789001895863332576) -->
- <skip />
+ <string name="media_input_group_title" msgid="2057057473860783021">"ইনপুট"</string>
+ <string name="media_output_group_title" msgid="6789001895863332576">"আউটপুট"</string>
<string name="media_output_end_session_dialog_summary" msgid="5954520685989877347">"অন্য ডিভাইসে মিডিয়া সরাতে আপনার শেয়ার করা সেশন বন্ধ করুন"</string>
<string name="media_output_end_session_dialog_stop" msgid="208189434474624412">"বন্ধ করুন"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"ব্রডকাস্ট কীভাবে কাজ করে"</string>
@@ -1398,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"আইকন আড়াল করুন"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"আইকন বড় করুন"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"অথবা"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"টেনে আনার হ্যান্ডেল"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"আপনার কীবোর্ড ব্যবহার করে নেভিগেট করুন"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"কীবোর্ড শর্টকাট সম্পর্কে জানুন"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"আপনার টাচপ্যাড ব্যবহার করে নেভিগেট করুন"</string>
diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml
index 34e3d805ba35..679becec2b15 100644
--- a/packages/SystemUI/res/values-bs/strings.xml
+++ b/packages/SystemUI/res/values-bs/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"Razgovori"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Obriši sva nečujna obavještenja"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Obavještenja su pauzirana načinom rada Ne ometaj"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"Započni odmah"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"Nema obavještenja"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"Nema novih obavještenja"</string>
@@ -1396,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ikona sužavanja"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ikona proširivanja"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ili"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Ručica za prevlačenje"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Krećite se pomoću tastature"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Saznajte više o prečicama tastature"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Krećite se pomoću dodirne podloge"</string>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index 27f704d6c4d5..a0c0bf9defc8 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"Converses"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Esborra totes les notificacions silencioses"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Notificacions pausades pel mode No molestis"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"Comença ara"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"No hi ha cap notificació"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"No hi ha cap notificació nova"</string>
@@ -1396,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Replega la icona"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Desplega la icona"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"o"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Ansa per arrossegar"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Navega amb el teclat"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Aprèn les tecles de drecera"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Navega amb el ratolí tàctil"</string>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index c824837a73cd..107bac6b01a4 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"Konverzace"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Vymazat všechna tichá oznámení"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Oznámení jsou pozastavena režimem Nerušit"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"Spustit"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"Žádná oznámení"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"Žádná nová oznámení"</string>
@@ -1179,10 +1181,8 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Reproduktory a displeje"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Navrhovaná zařízení"</string>
- <!-- no translation found for media_input_group_title (2057057473860783021) -->
- <skip />
- <!-- no translation found for media_output_group_title (6789001895863332576) -->
- <skip />
+ <string name="media_input_group_title" msgid="2057057473860783021">"Vstup"</string>
+ <string name="media_output_group_title" msgid="6789001895863332576">"Výstup"</string>
<string name="media_output_end_session_dialog_summary" msgid="5954520685989877347">"Ukončí sdílenou relaci a bude možné přesunout médium do jiného zařízení"</string>
<string name="media_output_end_session_dialog_stop" msgid="208189434474624412">"Zastavit"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Jak vysílání funguje"</string>
@@ -1398,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ikona sbalení"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ikona rozbalení"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"nebo"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Úchyt pro přetažení"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Navigujte pomocí klávesnice"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Naučte se klávesové zkratky"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Navigujte pomocí touchpadu"</string>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index feac02dc0ac7..1ac75260f58d 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"Samtaler"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Ryd alle lydløse notifikationer"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Notifikationer er sat på pause af Forstyr ikke"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"Start nu"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"Ingen notifikationer"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"Ingen nye notifikationer"</string>
@@ -1179,10 +1181,8 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Højttalere og skærme"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Foreslåede enheder"</string>
- <!-- no translation found for media_input_group_title (2057057473860783021) -->
- <skip />
- <!-- no translation found for media_output_group_title (6789001895863332576) -->
- <skip />
+ <string name="media_input_group_title" msgid="2057057473860783021">"Input"</string>
+ <string name="media_output_group_title" msgid="6789001895863332576">"Output"</string>
<string name="media_output_end_session_dialog_summary" msgid="5954520685989877347">"Stop din delte session for at flytte medier til en anden enhed"</string>
<string name="media_output_end_session_dialog_stop" msgid="208189434474624412">"Stop"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Sådan fungerer udsendelser"</string>
@@ -1398,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ikon for Skjul"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ikon for Udvid"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"eller"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Håndtag"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Naviger ved hjælp af dit tastatur"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Se tastaturgenveje"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Naviger ved hjælp af din touchplade"</string>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index 3235c3ad8b79..8f5a705bc196 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"Unterhaltungen"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Alle lautlosen Benachrichtigungen löschen"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Benachrichtigungen durch „Bitte nicht stören“ pausiert"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"Jetzt starten"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"Keine Benachrichtigungen"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"Keine neuen Benachrichtigungen"</string>
@@ -1179,10 +1181,8 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Lautsprecher &amp; Displays"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Vorgeschlagene Geräte"</string>
- <!-- no translation found for media_input_group_title (2057057473860783021) -->
- <skip />
- <!-- no translation found for media_output_group_title (6789001895863332576) -->
- <skip />
+ <string name="media_input_group_title" msgid="2057057473860783021">"Eingabe"</string>
+ <string name="media_output_group_title" msgid="6789001895863332576">"Ausgabe"</string>
<string name="media_output_end_session_dialog_summary" msgid="5954520685989877347">"Beende die Freigabesitzung zur Übertragung von Medien auf das andere Gerät"</string>
<string name="media_output_end_session_dialog_stop" msgid="208189434474624412">"Beenden"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Funktionsweise von Nachrichten an alle"</string>
@@ -1293,8 +1293,7 @@
<string name="add" msgid="81036585205287996">"Hinzufügen"</string>
<string name="manage_users" msgid="1823875311934643849">"Nutzer verwalten"</string>
<string name="drag_split_not_supported" msgid="7173481676120546121">"Diese Benachrichtigung lässt sich nicht auf einen Splitscreen ziehen"</string>
- <!-- no translation found for dream_overlay_location_active (6484763493158166618) -->
- <skip />
+ <string name="dream_overlay_location_active" msgid="6484763493158166618">"Standortermittlung aktiv"</string>
<string name="dream_overlay_status_bar_wifi_off" msgid="4497069245055003582">"WLAN nicht verfügbar"</string>
<string name="dream_overlay_status_bar_priority_mode" msgid="5428462123314728739">"Prioritätsmodus"</string>
<string name="dream_overlay_status_bar_alarm_set" msgid="566707328356590886">"Wecker gestellt"</string>
@@ -1351,8 +1350,7 @@
<string name="lock_screen_settings" msgid="6152703934761402399">"Sperrbildschirm personalisieren"</string>
<string name="keyguard_unlock_to_customize_ls" msgid="2068542308086253819">"Zum Anpassen des Sperrbildschirms entsperren"</string>
<string name="wifi_unavailable_dream_overlay_content_description" msgid="2024166212194640100">"Kein WLAN verfügbar"</string>
- <!-- no translation found for location_active_dream_overlay_content_description (6208885541020673916) -->
- <skip />
+ <string name="location_active_dream_overlay_content_description" msgid="6208885541020673916">"Standortermittlung aktiv"</string>
<string name="camera_blocked_dream_overlay_content_description" msgid="4074759493559418130">"Kamera blockiert"</string>
<string name="camera_and_microphone_blocked_dream_overlay_content_description" msgid="7891078093416249764">"Kamera und Mikrofon blockiert"</string>
<string name="microphone_blocked_dream_overlay_content_description" msgid="5466897982130007033">"Mikrofon blockiert"</string>
@@ -1400,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Symbol „Minimieren“"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Symbol „Maximieren“"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"oder"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Ziehpunkt"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Navigation mit der Tastatur"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Informationen zu Tastenkombinationen"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Navigation mit dem Touchpad"</string>
@@ -1410,8 +1407,7 @@
<string name="launch_keyboard_touchpad_tutorial_notification_content" msgid="1780725168171929365">"Informationen zu Touchpad-Gesten, Tastenkombinationen und mehr"</string>
<string name="touchpad_tutorial_back_gesture_button" msgid="2746834288077265946">"Touch-Geste „Zurück“"</string>
<string name="touchpad_tutorial_home_gesture_button" msgid="7640544867625955304">"Touch-Geste „Startbildschirm“"</string>
- <!-- no translation found for touchpad_tutorial_recent_apps_gesture_button (8919227647650347359) -->
- <skip />
+ <string name="touchpad_tutorial_recent_apps_gesture_button" msgid="8919227647650347359">"Letzte Apps aufrufen"</string>
<string name="touchpad_tutorial_done_button" msgid="176168488821755503">"Fertig"</string>
<string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"Zurück"</string>
<string name="touchpad_back_gesture_guidance" msgid="6263750214998421587">"Wenn du zurückgehen möchtest, wische an einer beliebigen Stelle des Touchpads mit drei Fingern nach links oder rechts.\n\nDu kannst stattdessen auch die Tastenkombination „Aktion“ + „ESC“ verwenden."</string>
@@ -1421,14 +1417,10 @@
<string name="touchpad_home_gesture_guidance" msgid="3043931356096731966">"Du kannst jederzeit zum Startbildschirm gehen, indem du mit drei Fingern vom unteren Displayrand nach oben wischst."</string>
<string name="touchpad_home_gesture_success_title" msgid="3778407003948209795">"Sehr gut!"</string>
<string name="touchpad_home_gesture_success_body" msgid="2404031094918807067">"Du hast den Schritt für die „Startbildschirm“-Geste abgeschlossen."</string>
- <!-- no translation found for touchpad_recent_apps_gesture_action_title (934906836867137906) -->
- <skip />
- <!-- no translation found for touchpad_recent_apps_gesture_guidance (6012057247259983871) -->
- <skip />
- <!-- no translation found for touchpad_recent_apps_gesture_success_title (8481920554139332593) -->
- <skip />
- <!-- no translation found for touchpad_recent_apps_gesture_success_body (4334263906697493273) -->
- <skip />
+ <string name="touchpad_recent_apps_gesture_action_title" msgid="934906836867137906">"Letzte Apps aufrufen"</string>
+ <string name="touchpad_recent_apps_gesture_guidance" msgid="6012057247259983871">"Wische mit 3 Fingern nach oben und halte das Touchpad gedrückt."</string>
+ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"Gut gemacht!"</string>
+ <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"Du hast das Tutorial für die Touch-Geste zum Aufrufen der zuletzt verwendeten Apps abgeschlossen."</string>
<string name="tutorial_action_key_title" msgid="2659466586996495447">"Aktionstaste"</string>
<string name="tutorial_action_key_guidance" msgid="5718948664616999196">"Wenn du auf deine Apps zugreifen möchtest, drücke auf der Tastatur die Aktionstaste."</string>
<string name="tutorial_action_key_success_title" msgid="466467860120112933">"Glückwunsch!"</string>
@@ -1452,14 +1444,10 @@
<string name="overview_edu_notification_content" msgid="3578204677648432500">"Wische mit 3 Fingern nach oben und halte das Touchpad gedrückt. Tippe für mehr Infos zu Touch-Gesten."</string>
<string name="all_apps_edu_notification_title" msgid="372262997265569063">"Über die Tastatur alle Apps aufrufen"</string>
<string name="all_apps_edu_notification_content" msgid="3255070575694025585">"Du kannst jederzeit die Aktionstaste drücken. Tippe für mehr Infos zu Touch-Gesten."</string>
- <!-- no translation found for accessibility_deprecate_extra_dim_dialog_title (910988771011857460) -->
- <skip />
- <!-- no translation found for accessibility_deprecate_extra_dim_dialog_description (4453123359258743230) -->
- <skip />
- <!-- no translation found for accessibility_deprecate_extra_dim_dialog_button (3947537827396916005) -->
- <skip />
- <!-- no translation found for accessibility_deprecate_extra_dim_dialog_toast (165474092660941104) -->
- <skip />
+ <string name="accessibility_deprecate_extra_dim_dialog_title" msgid="910988771011857460">"„Extradunkel“ jetzt Teil des Schiebereglers für die Helligkeit"</string>
+ <string name="accessibility_deprecate_extra_dim_dialog_description" msgid="4453123359258743230">"Du kannst das Display jetzt extradunkel machen, indem du die Helligkeit noch weiter senkst.\n\nDa diese Funktion jetzt Teil des Schiebereglers für die Helligkeit ist, werden die „Extradunkel“-Kurzbefehle entfernt."</string>
+ <string name="accessibility_deprecate_extra_dim_dialog_button" msgid="3947537827396916005">"„Extradunkel“-Kurzbefehle entfernen"</string>
+ <string name="accessibility_deprecate_extra_dim_dialog_toast" msgid="165474092660941104">"Kurzbefehle für „Extradunkel“ entfernt"</string>
<string name="qs_edit_mode_category_connectivity" msgid="4559726936546032672">"Konnektivität"</string>
<string name="qs_edit_mode_category_accessibility" msgid="7969091385071475922">"Bedienungshilfen"</string>
<string name="qs_edit_mode_category_utilities" msgid="8123080090108420095">"Dienstprogramme"</string>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index 2187a172a4aa..183249bc3e16 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"Συζητήσεις"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Διαγραφή όλων των ειδοποιήσεων σε σίγαση"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Οι ειδοποιήσεις τέθηκαν σε παύση από τη λειτουργία \"Μην ενοχλείτε\""</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"Έναρξη τώρα"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"Δεν υπάρχουν ειδοποιήσεις"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"Δεν υπάρχουν νέες ειδοποιήσεις"</string>
@@ -1179,10 +1181,8 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Ηχεία και οθόνες"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Προτεινόμενες συσκευές"</string>
- <!-- no translation found for media_input_group_title (2057057473860783021) -->
- <skip />
- <!-- no translation found for media_output_group_title (6789001895863332576) -->
- <skip />
+ <string name="media_input_group_title" msgid="2057057473860783021">"Είσοδος"</string>
+ <string name="media_output_group_title" msgid="6789001895863332576">"Έξοδος"</string>
<string name="media_output_end_session_dialog_summary" msgid="5954520685989877347">"Διακόψτε την κοινόχρηστη περίοδο λειτουργίας για να μεταφέρετε μέσα σε άλλη συσκευή"</string>
<string name="media_output_end_session_dialog_stop" msgid="208189434474624412">"Διακοπή"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Πώς λειτουργεί η μετάδοση"</string>
@@ -1398,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Εικονίδιο σύμπτυξης"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Εικονίδιο ανάπτυξης"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ή"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Λαβή μεταφοράς"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Πλοήγηση με το πληκτρολόγιο"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Μάθετε συντομεύσεις πληκτρολογίου"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Πλοήγηση με την επιφάνεια αφής"</string>
diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml
index 3a961e2aeeb2..9b7ae5da01ed 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"Conversations"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Clear all silent notifications"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Notifications paused by Do Not Disturb"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"Start now"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"No notifications"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"No new notifications"</string>
@@ -1396,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Collapse icon"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Expand icon"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"or"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Drag handle"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Navigate using your keyboard"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Learn keyboards shortcuts"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Navigate using your touchpad"</string>
diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml
index ba71ccfd4256..4fcc7a4eb550 100644
--- a/packages/SystemUI/res/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res/values-en-rCA/strings.xml
@@ -573,6 +573,7 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"Conversations"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Clear all silent notifications"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Notifications paused by Do Not Disturb"</string>
+ <string name="modes_suppressing_shade_text" msgid="6037581130837903239">"{count,plural,offset:1 =0{No notifications}=1{Notifications paused by {mode}}=2{Notifications paused by {mode} and one other mode}other{Notifications paused by {mode} and # other modes}}"</string>
<string name="media_projection_action_text" msgid="3634906766918186440">"Start now"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"No notifications"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"No new notifications"</string>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index 3a961e2aeeb2..9b7ae5da01ed 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"Conversations"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Clear all silent notifications"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Notifications paused by Do Not Disturb"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"Start now"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"No notifications"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"No new notifications"</string>
@@ -1396,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Collapse icon"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Expand icon"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"or"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Drag handle"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Navigate using your keyboard"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Learn keyboards shortcuts"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Navigate using your touchpad"</string>
diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml
index 3a961e2aeeb2..9b7ae5da01ed 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"Conversations"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Clear all silent notifications"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Notifications paused by Do Not Disturb"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"Start now"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"No notifications"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"No new notifications"</string>
@@ -1396,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Collapse icon"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Expand icon"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"or"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Drag handle"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Navigate using your keyboard"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Learn keyboards shortcuts"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Navigate using your touchpad"</string>
diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml
index eb76a7edcb87..3c22ed163d31 100644
--- a/packages/SystemUI/res/values-en-rXC/strings.xml
+++ b/packages/SystemUI/res/values-en-rXC/strings.xml
@@ -573,6 +573,7 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‏‎‏‏‎‎‏‏‏‏‎‏‏‏‏‏‎‎‏‎‏‏‎‎‎‏‎‎‎‏‏‎‎‎‏‎‎‏‎‎‎‎‎‏‏‎‎‎‎‏‎‏‏‏‏‎‏‎Conversations‎‏‎‎‏‎"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‎‎‎‎‏‎‎‎‏‏‏‎‏‏‎‎‏‏‎‏‏‎‎‏‎‎‏‎‎‏‎‎‎‏‎‏‏‎‏‏‎‏‎‏‏‎‎‎‏‎‎‎‏‏‏‏‏‎Clear all silent notifications‎‏‎‎‏‎"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‎‏‏‎‎‎‏‏‎‏‎‏‏‏‎‏‎‏‏‏‎‏‎‎‎‎‎‏‏‎‏‎‏‎‎‏‎‏‏‎‎‎‎‎‏‎‏‎‏‏‎‎‏‎‏‎‏‎‎Notifications paused by Do Not Disturb‎‏‎‎‏‎"</string>
+ <string name="modes_suppressing_shade_text" msgid="6037581130837903239">"{count,plural,offset:1 =0{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‏‏‏‏‎‎‏‎‎‏‏‏‎‎‏‏‎‎‎‎‎‎‏‏‎‎‏‏‎‎‏‏‏‎‎‎‏‏‎‎‎‏‏‏‎‎‎‏‏‏‏‎‎‎‎‏‏‏‎No notifications‎‏‎‎‏‎}=1{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‏‏‏‏‎‎‏‎‎‏‏‏‎‎‏‏‎‎‎‎‎‎‏‏‎‎‏‏‎‎‏‏‏‎‎‎‏‏‎‎‎‏‏‏‎‎‎‏‏‏‏‎‎‎‎‏‏‏‎Notifications paused by ‎‏‎‎‏‏‎{mode}‎‏‎‎‏‏‏‎‎‏‎‎‏‎}=2{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‏‏‏‏‎‎‏‎‎‏‏‏‎‎‏‏‎‎‎‎‎‎‏‏‎‎‏‏‎‎‏‏‏‎‎‎‏‏‎‎‎‏‏‏‎‎‎‏‏‏‏‎‎‎‎‏‏‏‎Notifications paused by ‎‏‎‎‏‏‎{mode}‎‏‎‎‏‏‏‎ and one other mode‎‏‎‎‏‎}other{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‏‏‏‏‎‎‏‎‎‏‏‏‎‎‏‏‎‎‎‎‎‎‏‏‎‎‏‏‎‎‏‏‏‎‎‎‏‏‎‎‎‏‏‏‎‎‎‏‏‏‏‎‎‎‎‏‏‏‎Notifications paused by ‎‏‎‎‏‏‎{mode}‎‏‎‎‏‏‏‎ and # other modes‎‏‎‎‏‎}}"</string>
<string name="media_projection_action_text" msgid="3634906766918186440">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‎‎‏‏‏‎‎‎‏‏‏‎‎‏‎‎‎‎‏‏‏‎‎‏‎‎‎‏‎‏‎‎‏‏‏‎‏‎‎‏‎‎‏‏‏‎‏‎‏‏‏‎‎‏‎‎‎‎Start now‎‏‎‎‏‎"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‎‎‎‎‎‎‎‎‏‎‏‏‏‎‏‏‏‎‏‏‎‎‏‎‎‎‏‏‏‎‏‎‎‎‏‎‎‏‏‎‎‏‎‏‎‏‎‎‏‎‎‎‏‎‎‏‎‎‎No notifications‎‏‎‎‏‎"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‎‏‎‏‏‏‏‏‎‏‎‎‏‎‎‏‎‎‏‎‎‏‏‎‎‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‏‎‏‏‏‏‎‎‎‏‎‎‎‎‏‎‏‎‎No new notifications‎‏‎‎‏‎"</string>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index b1d0c23761ff..88cedcf8f1d1 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"Conversaciones"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Borrar todas las notificaciones silenciosas"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Notificaciones pausadas por el modo \"No interrumpir\""</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"Comenzar ahora"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"No hay notificaciones"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"No hay notificaciones nuevas"</string>
@@ -1179,10 +1181,8 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Bocinas y pantallas"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Dispositivos sugeridos"</string>
- <!-- no translation found for media_input_group_title (2057057473860783021) -->
- <skip />
- <!-- no translation found for media_output_group_title (6789001895863332576) -->
- <skip />
+ <string name="media_input_group_title" msgid="2057057473860783021">"Entrada"</string>
+ <string name="media_output_group_title" msgid="6789001895863332576">"Salida"</string>
<string name="media_output_end_session_dialog_summary" msgid="5954520685989877347">"Detiene la sesión de uso compartido para transferir contenido multimedia a otro dispositivo"</string>
<string name="media_output_end_session_dialog_stop" msgid="208189434474624412">"Detener"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Cómo funciona la transmisión"</string>
@@ -1293,8 +1293,7 @@
<string name="add" msgid="81036585205287996">"Agregar"</string>
<string name="manage_users" msgid="1823875311934643849">"Administrar usuarios"</string>
<string name="drag_split_not_supported" msgid="7173481676120546121">"Esta notificación no admite arrastrar entre pantallas divididas"</string>
- <!-- no translation found for dream_overlay_location_active (6484763493158166618) -->
- <skip />
+ <string name="dream_overlay_location_active" msgid="6484763493158166618">"Ubicación activa"</string>
<string name="dream_overlay_status_bar_wifi_off" msgid="4497069245055003582">"La red Wi-Fi no está disponible"</string>
<string name="dream_overlay_status_bar_priority_mode" msgid="5428462123314728739">"Modo prioridad"</string>
<string name="dream_overlay_status_bar_alarm_set" msgid="566707328356590886">"Se estableció la alarma"</string>
@@ -1351,8 +1350,7 @@
<string name="lock_screen_settings" msgid="6152703934761402399">"Personalizar pantalla de bloqueo"</string>
<string name="keyguard_unlock_to_customize_ls" msgid="2068542308086253819">"Desbloquea para personalizar la pantalla de bloqueo"</string>
<string name="wifi_unavailable_dream_overlay_content_description" msgid="2024166212194640100">"Wi-Fi no disponible"</string>
- <!-- no translation found for location_active_dream_overlay_content_description (6208885541020673916) -->
- <skip />
+ <string name="location_active_dream_overlay_content_description" msgid="6208885541020673916">"Ubicación activa"</string>
<string name="camera_blocked_dream_overlay_content_description" msgid="4074759493559418130">"La cámara está bloqueada"</string>
<string name="camera_and_microphone_blocked_dream_overlay_content_description" msgid="7891078093416249764">"La cámara y el micrófono están bloqueados"</string>
<string name="microphone_blocked_dream_overlay_content_description" msgid="5466897982130007033">"El micrófono está bloqueado"</string>
@@ -1400,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ícono de contraer"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ícono de expandir"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"o"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Controlador de arrastre"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Navega con el teclado"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Aprende combinaciones de teclas"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Navega con el panel táctil"</string>
@@ -1410,8 +1407,7 @@
<string name="launch_keyboard_touchpad_tutorial_notification_content" msgid="1780725168171929365">"Aprende sobre los gestos del panel táctil, las combinaciones de teclas y mucho más"</string>
<string name="touchpad_tutorial_back_gesture_button" msgid="2746834288077265946">"Gesto atrás"</string>
<string name="touchpad_tutorial_home_gesture_button" msgid="7640544867625955304">"Gesto para ir a la pantalla principal"</string>
- <!-- no translation found for touchpad_tutorial_recent_apps_gesture_button (8919227647650347359) -->
- <skip />
+ <string name="touchpad_tutorial_recent_apps_gesture_button" msgid="8919227647650347359">"Ver apps recientes"</string>
<string name="touchpad_tutorial_done_button" msgid="176168488821755503">"Listo"</string>
<string name="touchpad_back_gesture_action_title" msgid="7199067250654332735">"Atrás"</string>
<string name="touchpad_back_gesture_guidance" msgid="6263750214998421587">"Para volver, desliza tres dedos hacia la derecha o la izquierda en cualquier lugar del panel táctil.\n\nPara completar esta acción, también puedes usar la combinación de teclas Action + ESC."</string>
@@ -1421,14 +1417,10 @@
<string name="touchpad_home_gesture_guidance" msgid="3043931356096731966">"Para ir a la pantalla principal en cualquier momento, desliza hacia arriba desde la parte inferior de la pantalla con tres dedos."</string>
<string name="touchpad_home_gesture_success_title" msgid="3778407003948209795">"¡Muy bien!"</string>
<string name="touchpad_home_gesture_success_body" msgid="2404031094918807067">"Completaste el gesto para ir al inicio."</string>
- <!-- no translation found for touchpad_recent_apps_gesture_action_title (934906836867137906) -->
- <skip />
- <!-- no translation found for touchpad_recent_apps_gesture_guidance (6012057247259983871) -->
- <skip />
- <!-- no translation found for touchpad_recent_apps_gesture_success_title (8481920554139332593) -->
- <skip />
- <!-- no translation found for touchpad_recent_apps_gesture_success_body (4334263906697493273) -->
- <skip />
+ <string name="touchpad_recent_apps_gesture_action_title" msgid="934906836867137906">"Ver apps recientes"</string>
+ <string name="touchpad_recent_apps_gesture_guidance" msgid="6012057247259983871">"Desliza hacia arriba con tres dedos en el panel táctil y mantenlos presionados."</string>
+ <string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"¡Bien hecho!"</string>
+ <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"Completaste el gesto para ver las apps recientes."</string>
<string name="tutorial_action_key_title" msgid="2659466586996495447">"Tecla de acción"</string>
<string name="tutorial_action_key_guidance" msgid="5718948664616999196">"Para acceder a las apps, presiona la tecla de acción en el teclado."</string>
<string name="tutorial_action_key_success_title" msgid="466467860120112933">"¡Felicitaciones!"</string>
@@ -1452,14 +1444,10 @@
<string name="overview_edu_notification_content" msgid="3578204677648432500">"Desliza hacia arriba con tres dedos y mantenlos presionados. Presiona para aprender más gestos."</string>
<string name="all_apps_edu_notification_title" msgid="372262997265569063">"Usa el teclado para ver todas las apps"</string>
<string name="all_apps_edu_notification_content" msgid="3255070575694025585">"Presiona la tecla de acción en cualquier momento. Presiona para aprender más gestos."</string>
- <!-- no translation found for accessibility_deprecate_extra_dim_dialog_title (910988771011857460) -->
- <skip />
- <!-- no translation found for accessibility_deprecate_extra_dim_dialog_description (4453123359258743230) -->
- <skip />
- <!-- no translation found for accessibility_deprecate_extra_dim_dialog_button (3947537827396916005) -->
- <skip />
- <!-- no translation found for accessibility_deprecate_extra_dim_dialog_toast (165474092660941104) -->
- <skip />
+ <string name="accessibility_deprecate_extra_dim_dialog_title" msgid="910988771011857460">"La atenuación extra ahora es parte del control deslizante de brillo"</string>
+ <string name="accessibility_deprecate_extra_dim_dialog_description" msgid="4453123359258743230">"Ahora puedes bajar el nivel del brillo de la pantalla para atenuarla aún más.\n\nComo esta función ahora es parte del control deslizante de brillo, se quitaron los accesos directos de atenuación extra."</string>
+ <string name="accessibility_deprecate_extra_dim_dialog_button" msgid="3947537827396916005">"Quitar accesos directos a la atenuación extra"</string>
+ <string name="accessibility_deprecate_extra_dim_dialog_toast" msgid="165474092660941104">"Se quitaron los accesos directos a la atenuación extra"</string>
<string name="qs_edit_mode_category_connectivity" msgid="4559726936546032672">"Conectividad"</string>
<string name="qs_edit_mode_category_accessibility" msgid="7969091385071475922">"Accesibilidad"</string>
<string name="qs_edit_mode_category_utilities" msgid="8123080090108420095">"Utilidades"</string>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index 21b42ad2bfb0..63f4ae11a7f6 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"Conversaciones"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Borrar todas las notificaciones silenciosas"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Notificaciones pausadas por el modo No molestar"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"Empezar ahora"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"No hay notificaciones"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"No hay notificaciones nuevas"</string>
@@ -1179,10 +1181,8 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Altavoces y pantallas"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Dispositivos sugeridos"</string>
- <!-- no translation found for media_input_group_title (2057057473860783021) -->
- <skip />
- <!-- no translation found for media_output_group_title (6789001895863332576) -->
- <skip />
+ <string name="media_input_group_title" msgid="2057057473860783021">"Entrada"</string>
+ <string name="media_output_group_title" msgid="6789001895863332576">"Salida"</string>
<string name="media_output_end_session_dialog_summary" msgid="5954520685989877347">"Detén tu sesión compartida para transferir el contenido multimedia a otro dispositivo"</string>
<string name="media_output_end_session_dialog_stop" msgid="208189434474624412">"Detener"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Cómo funciona la emisión"</string>
@@ -1398,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Icono de contraer"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Icono de desplegar"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"o"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Controlador de arrastre"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Desplázate con el teclado"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Aprende combinaciones de teclas"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Desplázate con el panel táctil"</string>
diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml
index c1807ad7d208..35d40120c802 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"Vestlused"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Kustuta kõik hääletud märguanded"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Režiim Mitte segada peatas märguanded"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"Alusta kohe"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"Märguandeid pole"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"Uusi märguandeid ei ole"</string>
@@ -1179,10 +1181,8 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Kõlarid ja ekraanid"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Soovitatud seadmed"</string>
- <!-- no translation found for media_input_group_title (2057057473860783021) -->
- <skip />
- <!-- no translation found for media_output_group_title (6789001895863332576) -->
- <skip />
+ <string name="media_input_group_title" msgid="2057057473860783021">"Sisend"</string>
+ <string name="media_output_group_title" msgid="6789001895863332576">"Väljund"</string>
<string name="media_output_end_session_dialog_summary" msgid="5954520685989877347">"Peatage jagatud seanss, et meedia teise seadmesse teisaldada"</string>
<string name="media_output_end_session_dialog_stop" msgid="208189434474624412">"Peata"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Kuidas ülekandmine toimib?"</string>
@@ -1398,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ahendamisikoon"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Laiendamisikoon"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"või"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Lohistamispide"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Navigeerige klaviatuuri abil"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Õppige klaviatuuri otseteid"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Navigeerige puuteplaadi abil"</string>
diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml
index 50a55f921347..7e0f203d0b0b 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"Elkarrizketak"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Garbitu soinurik gabeko jakinarazpen guztiak"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Ez molestatzeko moduak pausatu egin ditu jakinarazpenak"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"Hasi"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"Ez dago jakinarazpenik"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"Ez dago jakinarazpen berririk"</string>
@@ -1179,10 +1181,8 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"%% <xliff:g id="PERCENTAGE">%1$d</xliff:g>"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Bozgorailuak eta pantailak"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Iradokitako gailuak"</string>
- <!-- no translation found for media_input_group_title (2057057473860783021) -->
- <skip />
- <!-- no translation found for media_output_group_title (6789001895863332576) -->
- <skip />
+ <string name="media_input_group_title" msgid="2057057473860783021">"Sarrera"</string>
+ <string name="media_output_group_title" msgid="6789001895863332576">"Irteera"</string>
<string name="media_output_end_session_dialog_summary" msgid="5954520685989877347">"Gelditu partekatutako saioa multimedia-edukia beste gailu batera eramateko"</string>
<string name="media_output_end_session_dialog_stop" msgid="208189434474624412">"Gelditu"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Nola funtzionatzen dute iragarpenek?"</string>
@@ -1398,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Tolesteko ikonoa"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Zabaltzeko ikonoa"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"edo"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Arrastatzeko kontrol-puntua"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Nabigatu teklatua erabilita"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Ikasi lasterbideak"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Nabigatu ukipen-panela erabilita"</string>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index 8f413c8f859d..e70d2bb1ba95 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"مکالمه‌ها"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"پاک کردن همه اعلان‌های بی‌صدا"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"اعلان‌ها توسط «مزاحم نشوید» موقتاً متوقف شدند"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"اکنون شروع کنید"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"اعلانی موجود نیست"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"اعلان جدیدی وجود ندارد"</string>
@@ -1022,7 +1024,7 @@
<string name="privacy_type_location" msgid="7991481648444066703">"مکان"</string>
<string name="privacy_type_microphone" msgid="9136763906797732428">"میکروفون"</string>
<string name="privacy_type_media_projection" msgid="8136723828804251547">"ضبط صفحه‌نمایش"</string>
- <string name="music_controls_no_title" msgid="4166497066552290938">"بدون عنوان"</string>
+ <string name="music_controls_no_title" msgid="4166497066552290938">"بی‌عنوان"</string>
<string name="inattentive_sleep_warning_title" msgid="3891371591713990373">"آماده‌به‌کار"</string>
<string name="font_scaling_dialog_title" msgid="6273107303850248375">"اندازه قلم"</string>
<string name="font_scaling_smaller" msgid="1012032217622008232">"کوچک‌تر کردن"</string>
@@ -1396,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"نماد جمع کردن"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"نماد ازهم بازکردن"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"یا"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"دستگیره کشاندن"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"پیمایش کردن بااستفاده از صفحه‌کلید"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"آشنایی با میان‌برهای صفحه‌کلید"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"پیمایش کردن بااستفاده از صفحه لمسی"</string>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index f8fa57d72d27..f6d43c1a91d1 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"Keskustelut"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Tyhjennä kaikki hiljaiset ilmoitukset"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Älä häiritse ‑tila keskeytti ilmoitukset"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"Aloita nyt"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"Ei ilmoituksia"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"Ei uusia ilmoituksia"</string>
@@ -1179,10 +1181,8 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Kaiuttimet ja näytöt"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Ehdotetut laitteet"</string>
- <!-- no translation found for media_input_group_title (2057057473860783021) -->
- <skip />
- <!-- no translation found for media_output_group_title (6789001895863332576) -->
- <skip />
+ <string name="media_input_group_title" msgid="2057057473860783021">"Sisääntulo"</string>
+ <string name="media_output_group_title" msgid="6789001895863332576">"Ulostulo"</string>
<string name="media_output_end_session_dialog_summary" msgid="5954520685989877347">"Lopeta jaettu istunto, jotta voit siirtyä mediaan toisella laitteella"</string>
<string name="media_output_end_session_dialog_stop" msgid="208189434474624412">"Lopeta"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Miten lähetys toimii"</string>
@@ -1398,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Tiivistyskuvake"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Laajennuskuvake"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"tai"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Vetokahva"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Siirry käyttämällä näppäimistöä"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Opettele pikanäppäimiä"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Siirry käyttämällä kosketuslevyä"</string>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index 106af39f6d1b..1f6de9f6a4b1 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"Conversations"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Effacer toutes les notifications silencieuses"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Les notifications sont suspendues par le mode Ne pas déranger"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"Commencer"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"Aucune notification"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"Aucune nouvelle notification"</string>
@@ -1179,10 +1181,8 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Haut-parleurs et écrans"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Appareils suggérés"</string>
- <!-- no translation found for media_input_group_title (2057057473860783021) -->
- <skip />
- <!-- no translation found for media_output_group_title (6789001895863332576) -->
- <skip />
+ <string name="media_input_group_title" msgid="2057057473860783021">"Entrer"</string>
+ <string name="media_output_group_title" msgid="6789001895863332576">"Sortie"</string>
<string name="media_output_end_session_dialog_summary" msgid="5954520685989877347">"Arrêtez votre session partagée pour déplacer des contenus multimédias vers un autre appareil"</string>
<string name="media_output_end_session_dialog_stop" msgid="208189434474624412">"Arrêter"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Fonctionnement de la diffusion"</string>
@@ -1398,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Icône Réduire"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Icône Développer"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ou"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Poignée de déplacement"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Naviguer à l\'aide de votre clavier"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Apprenez à utiliser les raccourcis-clavier"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Naviguer à l\'aide de votre pavé tactile"</string>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index ad7a87595cab..fbf5a6c7aced 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"Conversations"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Effacer toutes les notifications silencieuses"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Notifications suspendues par le mode Ne pas déranger"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"Commencer"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"Aucune notification"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"Aucune nouvelle notification"</string>
@@ -1179,10 +1181,8 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Enceintes et écrans"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Appareils suggérés"</string>
- <!-- no translation found for media_input_group_title (2057057473860783021) -->
- <skip />
- <!-- no translation found for media_output_group_title (6789001895863332576) -->
- <skip />
+ <string name="media_input_group_title" msgid="2057057473860783021">"Entrée"</string>
+ <string name="media_output_group_title" msgid="6789001895863332576">"Sortie"</string>
<string name="media_output_end_session_dialog_summary" msgid="5954520685989877347">"Arrêter votre session partagée pour déplacer des contenus multimédias vers un autre appareil"</string>
<string name="media_output_end_session_dialog_stop" msgid="208189434474624412">"Arrêter"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Fonctionnement des annonces"</string>
@@ -1398,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Icône Réduire"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Icône Développer"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ou"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Poignée de déplacement"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Naviguer à l\'aide du clavier"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Découvrir les raccourcis clavier"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Naviguer à l\'aide de votre pavé tactile"</string>
diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml
index bfe5b58d7f17..b9d0891b99fa 100644
--- a/packages/SystemUI/res/values-gl/strings.xml
+++ b/packages/SystemUI/res/values-gl/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"Conversas"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Borrar todas as notificacións silenciadas"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"O modo Non molestar puxo en pausa as notificacións"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"Iniciar agora"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"Non hai notificacións"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"Non hai notificacións novas"</string>
@@ -1179,10 +1181,8 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Altofalantes e pantallas"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Dispositivos suxeridos"</string>
- <!-- no translation found for media_input_group_title (2057057473860783021) -->
- <skip />
- <!-- no translation found for media_output_group_title (6789001895863332576) -->
- <skip />
+ <string name="media_input_group_title" msgid="2057057473860783021">"Entrada"</string>
+ <string name="media_output_group_title" msgid="6789001895863332576">"Saída"</string>
<string name="media_output_end_session_dialog_summary" msgid="5954520685989877347">"Detén a sesión de uso compartido para mover contido multimedia a outro dispositivo"</string>
<string name="media_output_end_session_dialog_stop" msgid="208189434474624412">"Deter"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Como funcionan as difusións?"</string>
@@ -1398,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Icona de contraer"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Icona de despregar"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ou"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Controlador de arrastre"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Navega co teclado"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Aprende a usar os atallos de teclado"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Navega co panel táctil"</string>
diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml
index 5d952ac1f4cd..e9998e448595 100644
--- a/packages/SystemUI/res/values-gu/strings.xml
+++ b/packages/SystemUI/res/values-gu/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"વાતચીત"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"બધા સાઇલન્ટ નોટિફિકેશન સાફ કરો"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"ખલેલ પાડશો નહીં દ્વારા થોભાવેલ નોટિફિકેશન"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"હવે શરૂ કરો"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"કોઈ નોટિફિકેશન નથી"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"કોઈ નવું નોટિફિકેશન નથી"</string>
@@ -1396,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"\'નાનું કરો\'નું આઇકન"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"\'મોટું કરો\'નું આઇકન"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"અથવા"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"ઑબ્જેક્ટ ખેંચવાનું હૅન્ડલ"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"તમારા કીબોર્ડ વડે નૅવિગેટ કરો"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"કીબોર્ડ શૉર્ટકર્ટ જાણો"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"તમારા ટચપૅડ વડે નૅવિગેટ કરો"</string>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index 03028b14b7d1..534da68f0286 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -573,10 +573,12 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"बातचीत"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"बिना आवाज़ की सभी सूचनाएं हटाएं"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"\'परेशान न करें\' सुविधा के ज़रिए कुछ समय के लिए सूचनाएं दिखाना रोक दिया गया है"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"अभी शुरू करें"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"कोई सूचना नहीं है"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"कोई नई सूचना नहीं है"</string>
- <string name="adaptive_notification_edu_hun_title" msgid="7790738150177329960">"नोटिफ़िकेशन कूलडाउन की सेटिंग चालू है"</string>
+ <string name="adaptive_notification_edu_hun_title" msgid="7790738150177329960">"लगातार सूचनाएं आने पर आवाज़ कम करने की सेटिंग चालू है"</string>
<string name="adaptive_notification_edu_hun_text" msgid="7743367744129536610">"एक साथ कई सूचनाएं मिलने पर, डिवाइस में सूचनाओं से होने वाली आवाज़ और सूचनाएं, दो मिनट के लिए अपने-आप कम हो जाएंगी."</string>
<string name="go_to_adaptive_notification_settings" msgid="2423690125178298479">"बंद करें"</string>
<string name="unlock_to_see_notif_text" msgid="7439033907167561227">"पुरानी सूचाएं देखने के लिए अनलॉक करें"</string>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index 4055e7441675..0d226b1db920 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"Razgovori"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Izbriši sve bešumne obavijesti"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Značajka Ne uznemiravaj pauzirala je Obavijesti"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"Pokreni"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"Nema obavijesti"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"Nema novih obavijesti"</string>
@@ -1396,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ikona za sažimanje"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ikona za proširivanje"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ili"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Marker za povlačenje"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Krećite se pomoću tipkovnice"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Saznajte više o tipkovnim prečacima"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Krećite se pomoću dodirne podloge"</string>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index 685e21930378..6c26cd5f817e 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"Beszélgetések"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Az összes néma értesítés törlése"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Ne zavarjanak funkcióval szüneteltetett értesítések"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"Indítás most"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"Nincs értesítés"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"Nincsenek új értesítések"</string>
@@ -1179,10 +1181,8 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Hangfalak és kijelzők"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Javasolt eszközök"</string>
- <!-- no translation found for media_input_group_title (2057057473860783021) -->
- <skip />
- <!-- no translation found for media_output_group_title (6789001895863332576) -->
- <skip />
+ <string name="media_input_group_title" msgid="2057057473860783021">"Bemenet"</string>
+ <string name="media_output_group_title" msgid="6789001895863332576">"Kimenet"</string>
<string name="media_output_end_session_dialog_summary" msgid="5954520685989877347">"Állítsa le a megosztott munkamenetet, ha át szeretné helyezni a médiát egy másik eszközre"</string>
<string name="media_output_end_session_dialog_stop" msgid="208189434474624412">"Leállítás"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"A közvetítés működése"</string>
diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml
index 36d3e42791fe..c0e7c309fef2 100644
--- a/packages/SystemUI/res/values-hy/strings.xml
+++ b/packages/SystemUI/res/values-hy/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"Զրույցներ"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Ջնջել բոլոր անձայն ծանուցումները"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Ծանուցումները չեն ցուցադրվի «Չանհանգստացնել» ռեժիմում"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"Սկսել հիմա"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"Ծանուցումներ չկան"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"Նոր ծանուցումներ չկան"</string>
@@ -1179,10 +1181,8 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Բարձրախոսներ և էկրաններ"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Առաջարկվող սարքեր"</string>
- <!-- no translation found for media_input_group_title (2057057473860783021) -->
- <skip />
- <!-- no translation found for media_output_group_title (6789001895863332576) -->
- <skip />
+ <string name="media_input_group_title" msgid="2057057473860783021">"Ներածում"</string>
+ <string name="media_output_group_title" msgid="6789001895863332576">"Արտածում"</string>
<string name="media_output_end_session_dialog_summary" msgid="5954520685989877347">"Կանգնեցրեք ընդհանուր աշխատաշրջանը՝ մուլտիմեդիա բովանդակությունն այլ սարք տեղափոխելու համար"</string>
<string name="media_output_end_session_dialog_stop" msgid="208189434474624412">"Կանգնեցնել"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Ինչպես է աշխատում հեռարձակումը"</string>
@@ -1398,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ծալել պատկերակը"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ծավալել պատկերակը"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"կամ"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Տեղափոխման նշիչ"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Կողմնորոշվեք ձեր ստեղնաշարի օգնությամբ"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Սովորեք օգտագործել ստեղնային դյուրանցումները"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Կողմնորոշվեք ձեր հպահարթակի օգնությամբ"</string>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index a5317258498c..906fde458d82 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"Percakapan"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Hapus semua notifikasi senyap"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Notifikasi dijeda oleh mode Jangan Ganggu"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"Mulai sekarang"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"Tidak ada notifikasi"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"Tidak ada notifikasi baru"</string>
@@ -1179,10 +1181,8 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Speaker &amp; Layar"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Perangkat yang Disarankan"</string>
- <!-- no translation found for media_input_group_title (2057057473860783021) -->
- <skip />
- <!-- no translation found for media_output_group_title (6789001895863332576) -->
- <skip />
+ <string name="media_input_group_title" msgid="2057057473860783021">"Input"</string>
+ <string name="media_output_group_title" msgid="6789001895863332576">"Output"</string>
<string name="media_output_end_session_dialog_summary" msgid="5954520685989877347">"Menghentikan sesi berbagi Anda untuk memindahkan media ke perangkat lain"</string>
<string name="media_output_end_session_dialog_stop" msgid="208189434474624412">"Berhenti"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Cara kerja siaran"</string>
@@ -1398,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ikon ciutkan"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ikon luaskan"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"atau"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Handel geser"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Menavigasi menggunakan keyboard"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Pelajari pintasan keyboard"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Menavigasi menggunakan touchpad"</string>
diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml
index c2ada41cc902..68f2c23e4a19 100644
--- a/packages/SystemUI/res/values-is/strings.xml
+++ b/packages/SystemUI/res/values-is/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"Samtöl"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Hreinsa allar þöglar tilkynningar"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Hlé gert á tilkynningum þar sem stillt er á „Ónáðið ekki“"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"Byrja núna"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"Engar tilkynningar"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"Engar nýjar tilkynningar"</string>
@@ -1179,10 +1181,8 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Hátalarar og skjáir"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Tillögur að tækjum"</string>
- <!-- no translation found for media_input_group_title (2057057473860783021) -->
- <skip />
- <!-- no translation found for media_output_group_title (6789001895863332576) -->
- <skip />
+ <string name="media_input_group_title" msgid="2057057473860783021">"Inntak"</string>
+ <string name="media_output_group_title" msgid="6789001895863332576">"Úttak"</string>
<string name="media_output_end_session_dialog_summary" msgid="5954520685989877347">"Stöðvaðu sameiginlega lotu til að flytja efni yfir í annað tæki"</string>
<string name="media_output_end_session_dialog_stop" msgid="208189434474624412">"Stöðva"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Svona virkar útsending"</string>
@@ -1398,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Minnka tákn"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Stækka tákn"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"eða"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Dragkló"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Flettu með því að nota lyklaborðið"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Kynntu þér flýtilykla"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Flettu með því að nota snertiflötinn"</string>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index 4ff2eda198fe..4f8015ce31a2 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"Conversazioni"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Cancella tutte le notifiche silenziose"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Notifiche messe in pausa in base alla modalità Non disturbare"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"Avvia adesso"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"Nessuna notifica"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"Nessuna nuova notifica"</string>
@@ -1179,10 +1181,8 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Speaker e display"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Dispositivi consigliati"</string>
- <!-- no translation found for media_input_group_title (2057057473860783021) -->
- <skip />
- <!-- no translation found for media_output_group_title (6789001895863332576) -->
- <skip />
+ <string name="media_input_group_title" msgid="2057057473860783021">"Input"</string>
+ <string name="media_output_group_title" msgid="6789001895863332576">"Output"</string>
<string name="media_output_end_session_dialog_summary" msgid="5954520685989877347">"Interrompi la sessione condivisa per spostare i contenuti multimediali su un altro dispositivo"</string>
<string name="media_output_end_session_dialog_stop" msgid="208189434474624412">"Interrompi"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Come funziona la trasmissione"</string>
@@ -1398,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Icona Comprimi"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Icona Espandi"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"oppure"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Punto di trascinamento"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Naviga usando la tastiera"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Informazioni sulle scorciatoie da tastiera"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Naviga usando il touchpad"</string>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index 1c212e12a0ea..3d7a25c0e3ea 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"שיחות"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"ניקוי כל ההתראות השקטות"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"התראות הושהו על ידי מצב \'נא לא להפריע\'"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"כן, אפשר להתחיל"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"אין התראות"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"אין התראות חדשות"</string>
@@ -1179,10 +1181,8 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"‎<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%‎‎"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"רמקולים ומסכים"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"הצעות למכשירים"</string>
- <!-- no translation found for media_input_group_title (2057057473860783021) -->
- <skip />
- <!-- no translation found for media_output_group_title (6789001895863332576) -->
- <skip />
+ <string name="media_input_group_title" msgid="2057057473860783021">"קלט"</string>
+ <string name="media_output_group_title" msgid="6789001895863332576">"פלט"</string>
<string name="media_output_end_session_dialog_summary" msgid="5954520685989877347">"עצירת הסשן המשותף כדי להעביר מדיה למכשיר אחר"</string>
<string name="media_output_end_session_dialog_stop" msgid="208189434474624412">"עצירה"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"הסבר על שידורים"</string>
@@ -1398,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"סמל הכיווץ"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"סמל ההרחבה"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"או"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"נקודת האחיזה לגרירה"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"ניווט באמצעות המקלדת"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"מידע על מקשי קיצור"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"ניווט באמצעות לוח המגע"</string>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index eda727f21232..3e29ed62fa7c 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"会話"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"サイレント通知がすべて消去されます"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"サイレント モードにより通知は一時停止中です"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"今すぐ開始"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"通知はありません"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"新しい通知はありません"</string>
@@ -1396,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"閉じるアイコン"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"開くアイコン"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"または"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"ドラッグ ハンドル"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"キーボードを使用して移動する"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"キーボード ショートカットの詳細"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"タッチパッドを使用して移動する"</string>
diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml
index 836bd44b52bd..539c186011e3 100644
--- a/packages/SystemUI/res/values-ka/strings.xml
+++ b/packages/SystemUI/res/values-ka/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"საუბრები"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"ყველა ჩუმი შეტყობინების გასუფთავება"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"შეტყობინებები დაპაუზდა „არ შემაწუხოთ“ რეჟიმის მეშვეობით"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"დაწყება ახლავე"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"შეტყობინებები არ არის."</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"ახალი შეტყობინებები არ არის"</string>
@@ -1396,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"ხატულის ჩაკეცვა"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"ხატულის გაფართოება"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ან"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"სახელური ჩავლებისთვის"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"ნავიგაცია კლავიატურის გამოყენებით"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"კლავიატურის მალსახმობების სწავლა"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"ნავიგაცია სენსორული პანელის გამოყენებით"</string>
diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml
index dc2a618fc396..7343fa1440d6 100644
--- a/packages/SystemUI/res/values-kk/strings.xml
+++ b/packages/SystemUI/res/values-kk/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"Әңгімелер"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Барлық үнсіз хабарландыруларды өшіру"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Хабарландырулар Мазаламау режимінде кідіртілді"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"Қазір бастау"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"Хабарландырулар жоқ"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"Жаңа хабарландырулар жоқ"</string>
@@ -1179,10 +1181,8 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Динамиктер мен дисплейлер"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Ұсынылған құрылғылар"</string>
- <!-- no translation found for media_input_group_title (2057057473860783021) -->
- <skip />
- <!-- no translation found for media_output_group_title (6789001895863332576) -->
- <skip />
+ <string name="media_input_group_title" msgid="2057057473860783021">"Кіріс"</string>
+ <string name="media_output_group_title" msgid="6789001895863332576">"Шығыс"</string>
<string name="media_output_end_session_dialog_summary" msgid="5954520685989877347">"Мультимедиа файлын басқа құрылғыға жылжыту үшін ортақ сеансты тоқтатыңыз."</string>
<string name="media_output_end_session_dialog_stop" msgid="208189434474624412">"Тоқтату"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Тарату қалай жүзеге асады"</string>
@@ -1398,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Жию белгішесі"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Жаю белгішесі"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"немесе"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Сүйрейтін тетік"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Пернетақтамен жұмыс істеңіз"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Перне тіркесімдерін үйреніңіз."</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Сенсорлық тақтамен жұмыс істеңіз"</string>
diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml
index 6c15c79fc53e..117463564fd3 100644
--- a/packages/SystemUI/res/values-km/strings.xml
+++ b/packages/SystemUI/res/values-km/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"ការសន្ទនា"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"សម្អាត​ការជូនដំណឹង​ស្ងាត់ទាំងអស់"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"ការជូនដំណឹង​បានផ្អាក​ដោយ​មុខងារកុំរំខាន"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"ចាប់ផ្ដើម​ឥឡូវ"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"គ្មាន​ការ​ជូនដំណឹង"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"គ្មាន​ការ​ជូន​ដំណឹង​​ថ្មីៗទេ"</string>
@@ -1396,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"រូបតំណាង \"បង្រួម\""</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"រូបតំណាង \"ពង្រីក\""</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ឬ"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"ដង​អូស"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"រុករកដោយប្រើក្ដារចុចរបស់អ្នក"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"ស្វែងយល់អំពីផ្លូវកាត់​ក្ដារ​ចុច"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"រុករកដោយប្រើផ្ទាំងប៉ះរបស់អ្នក"</string>
diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml
index a16f110d0230..52daecb89c63 100644
--- a/packages/SystemUI/res/values-kn/strings.xml
+++ b/packages/SystemUI/res/values-kn/strings.xml
@@ -83,7 +83,7 @@
<string name="screenshot_failed_to_save_user_locked_text" msgid="6156607948256936920">"ಸ್ಕ್ರೀನ್‌ಶಾಟ್ ಉಳಿಸುವ ಮೊದಲು ಸಾಧನವನ್ನು ಅನ್‌ಲಾಕ್ ಮಾಡಬೇಕು"</string>
<string name="screenshot_failed_to_save_unknown_text" msgid="1506621600548684129">"ಸ್ಕ್ರೀನ್‌ಶಾಟ್ ಅನ್ನು ಪುನಃ ತೆಗೆದುಕೊಳ್ಳಲು ಪ್ರಯತ್ನಿಸಿ"</string>
<string name="screenshot_failed_to_save_text" msgid="7232739948999195960">"ಸ್ಕ್ರೀನ್‌ಶಾಟ್ ಉಳಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ"</string>
- <string name="screenshot_failed_to_capture_text" msgid="7818288545874407451">"ಅಪ್ಲಿಕೇಶನ್ ಅಥವಾ ಸಂಸ್ಥೆಯು ಸ್ಕ್ರೀನ್‌ಶಾಟ್‌ಗಳನ್ನು ತೆಗೆಯುವುದನ್ನು ಅನುಮತಿಸುವುದಿಲ್ಲ"</string>
+ <string name="screenshot_failed_to_capture_text" msgid="7818288545874407451">"ಆ್ಯಪ್ ಅಥವಾ ಸಂಸ್ಥೆಯು ಸ್ಕ್ರೀನ್‌ಶಾಟ್‌ಗಳನ್ನು ತೆಗೆಯುವುದನ್ನು ಅನುಮತಿಸುವುದಿಲ್ಲ"</string>
<string name="screenshot_blocked_by_admin" msgid="5486757604822795797">"ಸ್ಕ್ರೀನ್‌ಶಾಟ್‌ಗಳನ್ನು ತೆಗೆದುಕೊಳ್ಳುವುದನ್ನು ನಿಮ್ಮ IT ನಿರ್ವಾಹಕರು ನಿರ್ಬಂಧಿಸಿದ್ದಾರೆ"</string>
<string name="screenshot_edit_label" msgid="8754981973544133050">"ಎಡಿಟ್ ಮಾಡಿ"</string>
<string name="screenshot_edit_description" msgid="3333092254706788906">"ಸ್ಕ್ರೀನ್‌ಶಾಟ್‌ ಅನ್ನು ಎಡಿಟ್ ಮಾಡಿ"</string>
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"ಸಂಭಾಷಣೆಗಳು"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"ಎಲ್ಲಾ ನಿಶ್ಶಬ್ಧ ಅಧಿಸೂಚನೆಗಳನ್ನು ತೆರವುಗೊಳಿಸಿ"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"ಅಡಚಣೆ ಮಾಡಬೇಡಿ ಎನ್ನುವ ಮೂಲಕ ಅಧಿಸೂಚನೆಗಳನ್ನು ವಿರಾಮಗೊಳಿಸಲಾಗಿದೆ"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"ಈಗ ಪ್ರಾರಂಭಿಸಿ"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"ಯಾವುದೇ ಅಧಿಸೂಚನೆಗಳಿಲ್ಲ"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"ಯಾವುದೇ ಹೊಸ ಅಧಿಸೂಚನೆಗಳಿಲ್ಲ"</string>
@@ -963,7 +965,7 @@
<string name="tuner_left" msgid="5758862558405684490">"ಎಡ"</string>
<string name="tuner_right" msgid="8247571132790812149">"ಬಲ"</string>
<string name="tuner_menu" msgid="363690665924769420">"ಮೆನು"</string>
- <string name="tuner_app" msgid="6949280415826686972">"<xliff:g id="APP">%1$s</xliff:g> ಅಪ್ಲಿಕೇಶನ್"</string>
+ <string name="tuner_app" msgid="6949280415826686972">"<xliff:g id="APP">%1$s</xliff:g> ಆ್ಯಪ್"</string>
<string name="notification_channel_alerts" msgid="3385787053375150046">"ಅಲರ್ಟ್‌ಗಳು"</string>
<string name="notification_channel_battery" msgid="9219995638046695106">"ಬ್ಯಾಟರಿ"</string>
<string name="notification_channel_screenshot" msgid="7665814998932211997">"ಸ್ಕ್ರೀನ್‌ಶಾಟ್‌ಗಳು"</string>
@@ -986,8 +988,8 @@
<string name="dnd_is_off" msgid="3185706903793094463">"ಅಡಚಣೆ ಮಾಡಬೇಡಿ ಆಫ್ ಆಗಿದೆ"</string>
<string name="dnd_is_on" msgid="7009368176361546279">"ಅಡಚಣೆ ಮಾಡಬೇಡಿ ಮೋಡ್ ಆನ್ ಆಗಿದೆ"</string>
<string name="qs_dnd_prompt_auto_rule" msgid="3535469468310002616">"(<xliff:g id="ID_1">%s</xliff:g>) ಸ್ವಯಂಚಾಲಿತ ನಿಯಮದ ಮೂಲಕ ಅಡಚಣೆ ಮಾಡಬೇಡಿ ಆನ್ ಆಗಿದೆ."</string>
- <string name="qs_dnd_prompt_app" msgid="4027984447935396820">"(<xliff:g id="ID_1">%s</xliff:g>) ಅಪ್ಲಿಕೇಶನ್‌ ಮೂಲಕ ಅಡಚಣೆ ಮಾಡಬೇಡಿ ಆನ್ ಆಗಿದೆ."</string>
- <string name="qs_dnd_prompt_auto_rule_app" msgid="1841469944118486580">"ಸ್ವಯಂಚಾಲಿತ ನಿಯಮ ಅಥವಾ ಅಪ್ಲಿಕೇಶನ್‌ ಮೂಲಕ ಅಡಚಣೆ ಮಾಡಬೇಡಿ ಆನ್ ಆಗಿದೆ."</string>
+ <string name="qs_dnd_prompt_app" msgid="4027984447935396820">"(<xliff:g id="ID_1">%s</xliff:g>) ಆ್ಯಪ್‌ ಮೂಲಕ ಅಡಚಣೆ ಮಾಡಬೇಡಿ ಆನ್ ಆಗಿದೆ."</string>
+ <string name="qs_dnd_prompt_auto_rule_app" msgid="1841469944118486580">"ಸ್ವಯಂಚಾಲಿತ ನಿಯಮ ಅಥವಾ ಆ್ಯಪ್‌ ಮೂಲಕ ಅಡಚಣೆ ಮಾಡಬೇಡಿ ಆನ್ ಆಗಿದೆ."</string>
<string name="running_foreground_services_title" msgid="5137313173431186685">"ಅಪ್ಲಿಕೇಶನ್‌ಗಳು ಹಿನ್ನೆಲೆಯಲ್ಲಿ ರನ್ ಆಗುತ್ತಿವೆ"</string>
<string name="running_foreground_services_msg" msgid="3009459259222695385">"ಬ್ಯಾಟರಿ,ಡೇಟಾ ಬಳಕೆಯ ವಿವರಗಳಿಗಾಗಿ ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
<string name="mobile_data_disable_title" msgid="5366476131671617790">"ಮೊಬೈಲ್ ಡೇಟಾ ಆಫ್ ಮಾಡಬೇಕೆ?"</string>
@@ -997,7 +999,7 @@
<string name="auto_data_switch_disable_message" msgid="5885533647399535852">"ಲಭ್ಯತೆಯ ಆಧಾರದ ಮೇಲೆ ಮೊಬೈಲ್ ಡೇಟಾ ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಬದಲಾಗುವುದಿಲ್ಲ"</string>
<string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"ಬೇಡ, ಧನ್ಯವಾದಗಳು"</string>
<string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"ಹೌದು, ಬದಲಿಸಿ"</string>
- <string name="touch_filtered_warning" msgid="8119511393338714836">"ಅನುಮತಿ ವಿನಂತಿಯನ್ನು ಅಪ್ಲಿಕೇಶನ್ ಮರೆಮಾಚುತ್ತಿರುವ ಕಾರಣ, ಸೆಟ್ಟಿಂಗ್‌ಗಳಿಗೆ ನಿಮ್ಮ ಪ್ರತಿಕ್ರಿಯೆಯನ್ನು ಪರಿಶೀಲಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ."</string>
+ <string name="touch_filtered_warning" msgid="8119511393338714836">"ಅನುಮತಿ ವಿನಂತಿಯನ್ನು ಆ್ಯಪ್ ಮರೆಮಾಚುತ್ತಿರುವ ಕಾರಣ, ಸೆಟ್ಟಿಂಗ್‌ಗಳಿಗೆ ನಿಮ್ಮ ಪ್ರತಿಕ್ರಿಯೆಯನ್ನು ಪರಿಶೀಲಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ."</string>
<string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_2">%2$s</xliff:g> ಸ್ಲೈಸ್‌ಗಳನ್ನು ತೋರಿಸಲು <xliff:g id="APP_0">%1$s</xliff:g> ಅನ್ನು ಅನುಮತಿಸುವುದೇ?"</string>
<string name="slice_permission_text_1" msgid="6675965177075443714">"- ಇದು <xliff:g id="APP">%1$s</xliff:g> ನಿಂದ ಮಾಹಿತಿಯನ್ನು ಓದಬಹುದು"</string>
<string name="slice_permission_text_2" msgid="6758906940360746983">"- ಇದು <xliff:g id="APP">%1$s</xliff:g> ಒಳಗಡೆ ಕ್ರಿಯೆಗಳನ್ನು ತೆಗೆದುಕೊಳ್ಳಬಹುದು"</string>
@@ -1179,10 +1181,8 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"ಸ್ಪೀಕರ್‌ಗಳು ಮತ್ತು ಡಿಸ್‌ಪ್ಲೇಗಳು"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"ಸೂಚಿಸಿದ ಸಾಧನಗಳು"</string>
- <!-- no translation found for media_input_group_title (2057057473860783021) -->
- <skip />
- <!-- no translation found for media_output_group_title (6789001895863332576) -->
- <skip />
+ <string name="media_input_group_title" msgid="2057057473860783021">"ಇನ್‌ಪುಟ್"</string>
+ <string name="media_output_group_title" msgid="6789001895863332576">"ಔಟ್‌ಪುಟ್"</string>
<string name="media_output_end_session_dialog_summary" msgid="5954520685989877347">"ಮೀಡಿಯಾವನ್ನು ಮತ್ತೊಂದು ಸಾಧನಕ್ಕೆ ಸರಿಸಲು ನಿಮ್ಮ ಹಂಚಿಕೊಂಡ ಸೆಶನ್ ಅನ್ನು ನಿಲ್ಲಿಸಿ"</string>
<string name="media_output_end_session_dialog_stop" msgid="208189434474624412">"ನಿಲ್ಲಿಸಿ"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"ಪ್ರಸಾರವು ಹೇಗೆ ಕಾರ್ಯನಿರ್ವಹಿಸುತ್ತದೆ"</string>
@@ -1398,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"ಕುಗ್ಗಿಸುವ ಐಕಾನ್"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"ವಿಸ್ತೃತಗೊಳಿಸುವ ಐಕಾನ್"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ಅಥವಾ"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"ಡ್ರ್ಯಾಗ್‌ ಹ್ಯಾಂಡಲ್‌"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"ನಿಮ್ಮ ಕೀಬೋರ್ಡ್ ಬಳಸಿ ನ್ಯಾವಿಗೇಟ್ ಮಾಡಿ"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"ಕೀಬೋರ್ಡ್ ಶಾರ್ಟ್‌ಕಟ್‌ಗಳನ್ನು ಕಲಿಯಿರಿ"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"ನಿಮ್ಮ ಟಚ್‌ಪ್ಯಾಡ್ ಬಳಸಿ ನ್ಯಾವಿಗೇಟ್ ಮಾಡಿ"</string>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index 622f0fb127b2..9a9f3ebe71a8 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"대화"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"무음 알림 모두 삭제"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"방해 금지 모드로 알림이 일시중지됨"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"시작하기"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"알림 없음"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"새로운 알림 없음"</string>
@@ -1179,10 +1181,8 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"스피커 및 디스플레이"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"추천 기기"</string>
- <!-- no translation found for media_input_group_title (2057057473860783021) -->
- <skip />
- <!-- no translation found for media_output_group_title (6789001895863332576) -->
- <skip />
+ <string name="media_input_group_title" msgid="2057057473860783021">"입력"</string>
+ <string name="media_output_group_title" msgid="6789001895863332576">"출력"</string>
<string name="media_output_end_session_dialog_summary" msgid="5954520685989877347">"미디어를 다른 기기로 이동하려면 공유 세션을 중지하세요."</string>
<string name="media_output_end_session_dialog_stop" msgid="208189434474624412">"중지"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"브로드캐스팅 작동 원리"</string>
@@ -1398,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"접기 아이콘"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"확장 아이콘"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"또는"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"드래그 핸들"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"키보드를 사용하여 이동"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"단축키 알아보기"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"터치패드를 사용하여 이동"</string>
diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml
index 0edb3f82735a..33ebf6df3c11 100644
--- a/packages/SystemUI/res/values-ky/strings.xml
+++ b/packages/SystemUI/res/values-ky/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"Сүйлөшүүлөр"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Бардык үнсүз билдирмелерди өчүрүү"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"\"Тынчымды алба\" режиминде билдирмелер тындырылды"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"Азыр баштоо"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"Билдирме жок"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"Жаңы билдирмелер жок"</string>
@@ -1179,10 +1181,8 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Динамиктер жана дисплейлер"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Сунушталган түзмөктөр"</string>
- <!-- no translation found for media_input_group_title (2057057473860783021) -->
- <skip />
- <!-- no translation found for media_output_group_title (6789001895863332576) -->
- <skip />
+ <string name="media_input_group_title" msgid="2057057473860783021">"Киргизүү"</string>
+ <string name="media_output_group_title" msgid="6789001895863332576">"Чыккан маалымат"</string>
<string name="media_output_end_session_dialog_summary" msgid="5954520685989877347">"Медиафайлдарды башка түзмөккө жылдыруу үчүн жалпы сеансыңызды токтотуңуз"</string>
<string name="media_output_end_session_dialog_stop" msgid="208189434474624412">"Токтотуу"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Кабарлоо кантип иштейт"</string>
@@ -1398,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Жыйыштыруу сүрөтчөсү"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Жайып көрсөтүү сүрөтчөсү"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"же"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Cүйрөө маркери"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Нерселерге баскычтоп аркылуу өтүңүз"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Ыкчам баскычтар тууралуу билип алыңыз"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Нерселерге сенсордук такта аркылуу өтүңүз"</string>
diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml
index 242d766a7543..410f7d5a506f 100644
--- a/packages/SystemUI/res/values-lo/strings.xml
+++ b/packages/SystemUI/res/values-lo/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"ການສົນທະນາ"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"ລຶບລ້າງການແຈ້ງເຕືອນແບບງຽບທັງໝົດ"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"ຢຸດການແຈ້ງເຕືອນໂດຍໂໝດຫ້າມລົບກວນແລ້ວ"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"ເລີ່ມດຽວນີ້"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"ບໍ່ມີການແຈ້ງເຕືອນ"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"ບໍ່ມີການແຈ້ງເຕືອນໃໝ່"</string>
@@ -1179,10 +1181,8 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"ລຳໂພງ ແລະ ຈໍສະແດງຜົນ"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"ອຸປະກອນທີ່ແນະນຳ"</string>
- <!-- no translation found for media_input_group_title (2057057473860783021) -->
- <skip />
- <!-- no translation found for media_output_group_title (6789001895863332576) -->
- <skip />
+ <string name="media_input_group_title" msgid="2057057473860783021">"ອິນພຸດ"</string>
+ <string name="media_output_group_title" msgid="6789001895863332576">"ເອົ້າພຸດ"</string>
<string name="media_output_end_session_dialog_summary" msgid="5954520685989877347">"ຢຸດເຊດຊັນທີ່ແບ່ງປັນຂອງທ່ານເພື່ອຍ້າຍມີເດຍໄປຫາອຸປະກອນອື່ນ"</string>
<string name="media_output_end_session_dialog_stop" msgid="208189434474624412">"ຢຸດ"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"ການອອກອາກາດເຮັດວຽກແນວໃດ"</string>
@@ -1398,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"ໄອຄອນຫຍໍ້ລົງ"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"ໄອຄອນຂະຫຍາຍ"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ຫຼື"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"ບ່ອນຈັບລາກ"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"ນຳທາງໂດຍໃຊ້ແປ້ນພິມຂອງທ່ານ"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"ສຶກສາຄີລັດ"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"ນຳທາງໂດຍໃຊ້ແຜ່ນສຳຜັດຂອງທ່ານ"</string>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index fd625995c44d..67ead2399a38 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"Pokalbiai"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Išvalyti visus tylius pranešimus"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Pranešimai pristabdyti naudojant netrukdymo režimą"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"Pradėti dabar"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"Nėra įspėjimų"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"Naujų pranešimų nėra"</string>
@@ -1179,10 +1181,8 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Garsiakalbiai ir ekranai"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Siūlomi įrenginiai"</string>
- <!-- no translation found for media_input_group_title (2057057473860783021) -->
- <skip />
- <!-- no translation found for media_output_group_title (6789001895863332576) -->
- <skip />
+ <string name="media_input_group_title" msgid="2057057473860783021">"Įvestis"</string>
+ <string name="media_output_group_title" msgid="6789001895863332576">"Išvestis"</string>
<string name="media_output_end_session_dialog_summary" msgid="5954520685989877347">"Sustabdyti bendrinamą seansą norint perkelti mediją į kitą įrenginį"</string>
<string name="media_output_end_session_dialog_stop" msgid="208189434474624412">"Sustabdyti"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Kaip veikia transliacija"</string>
@@ -1398,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Sutraukimo piktograma"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Išskleidimo piktograma"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"arba"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Vilkimo rankenėlė"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Naršykite naudodamiesi klaviatūra"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Sužinokite apie sparčiuosius klavišus"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Naršykite naudodamiesi jutikline dalimi"</string>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index a6b7570a77e9..53e0baabcd28 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"Sarunas"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Notīrīt visus klusos paziņojumus"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Paziņojumi pārtraukti, izmantojot iestatījumu “Netraucēt”"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"Sākt tūlīt"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"Nav paziņojumu"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"Nav jaunu paziņojumu"</string>
@@ -1179,10 +1181,8 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Skaļruņi un displeji"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Ieteiktās ierīces"</string>
- <!-- no translation found for media_input_group_title (2057057473860783021) -->
- <skip />
- <!-- no translation found for media_output_group_title (6789001895863332576) -->
- <skip />
+ <string name="media_input_group_title" msgid="2057057473860783021">"Ievade"</string>
+ <string name="media_output_group_title" msgid="6789001895863332576">"Izvade"</string>
<string name="media_output_end_session_dialog_summary" msgid="5954520685989877347">"Pārtrauciet savu kopīgoto sesiju, lai pārvietotu multivides saturu uz citu ierīci."</string>
<string name="media_output_end_session_dialog_stop" msgid="208189434474624412">"Pārtraukt"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Kā darbojas apraide"</string>
@@ -1398,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Sakļaušanas ikona"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Izvēršanas ikona"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"vai"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Vilkšanas turis"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Pārvietošanās, izmantojot tastatūru"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Uzziniet par īsinājumtaustiņiem."</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Pārvietošanās, izmantojot skārienpaliktni"</string>
diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml
index 1773e3612771..86c93acada52 100644
--- a/packages/SystemUI/res/values-mk/strings.xml
+++ b/packages/SystemUI/res/values-mk/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"Разговори"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Избриши ги сите бесчујни известувања"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Известувањата се паузирани од „Не вознемирувај“"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"Започни сега"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"Нема известувања"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"Нема нови известувања"</string>
@@ -1396,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Икона за собирање"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Икона за проширување"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"или"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Рачка за влечење"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Движете се со користење на тастатурата"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Научете кратенки од тастатурата"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Движете се со користење на допирната подлога"</string>
diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml
index 05b76d3a3ef9..6958679712e6 100644
--- a/packages/SystemUI/res/values-ml/strings.xml
+++ b/packages/SystemUI/res/values-ml/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"സംഭാഷണങ്ങൾ"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"എല്ലാ നിശബ്‌ദ അറിയിപ്പുകളും മായ്ക്കുക"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"\'ശല്യപ്പെടുത്തരുത്\' വഴി അറിയിപ്പുകൾ താൽക്കാലികമായി നിർത്തി"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"ഇപ്പോൾ ആരംഭിക്കുക"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"അറിയിപ്പുകൾ ഒന്നുമില്ല"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"പുതിയ അറിയിപ്പുകളൊന്നുമില്ല"</string>
@@ -1396,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"ചുരുക്കൽ ഐക്കൺ"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"വികസിപ്പിക്കൽ ഐക്കൺ"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"അല്ലെങ്കിൽ"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"വലിച്ചിടുന്നതിനുള്ള ഹാൻഡിൽ"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"നിങ്ങളുടെ കീബോർഡ് ഉപയോഗിച്ച് നാവിഗേറ്റ് ചെയ്യുക"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"കീബോർഡ് കുറുക്കുവഴികൾ മനസ്സിലാക്കുക"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"നിങ്ങളുടെ ടച്ച്‌പാഡ് ഉപയോഗിച്ച് നാവിഗേറ്റ് ചെയ്യുക"</string>
diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml
index 7a9a38b8e4d7..95ff970ed0f4 100644
--- a/packages/SystemUI/res/values-mn/strings.xml
+++ b/packages/SystemUI/res/values-mn/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"Харилцан яриа"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Бүх чимээгүй мэдэгдлийг арилгах"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Бүү саад бол горимын түр зогсоосон мэдэгдэл"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"Одоо эхлүүлэх"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"Мэдэгдэл байхгүй"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"Шинэ мэдэгдэл алга"</string>
@@ -1179,10 +1181,8 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Чанга яригч ба дэлгэц"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Санал болгосон төхөөрөмжүүд"</string>
- <!-- no translation found for media_input_group_title (2057057473860783021) -->
- <skip />
- <!-- no translation found for media_output_group_title (6789001895863332576) -->
- <skip />
+ <string name="media_input_group_title" msgid="2057057473860783021">"Оролт"</string>
+ <string name="media_output_group_title" msgid="6789001895863332576">"Гаралт"</string>
<string name="media_output_end_session_dialog_summary" msgid="5954520685989877347">"Өөр төхөөрөмж рүү медиа зөөхийн тулд хуваалцсан харилцан үйлдлээ зогсооно уу"</string>
<string name="media_output_end_session_dialog_stop" msgid="208189434474624412">"Зогсоох"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Нэвтрүүлэлт хэрхэн ажилладаг вэ?"</string>
@@ -1398,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Хураах дүрс тэмдэг"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Дэлгэх дүрс тэмдэг"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"эсвэл"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Чирэх бариул"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Гараа ашиглан шилжих"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Товчлуурын шууд холбоосыг мэдэж аваарай"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Мэдрэгч самбараа ашиглан шилжээрэй"</string>
diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml
index e4063f629619..14739299f2ff 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"संभाषणे"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"सर्व सायलंट सूचना साफ करा"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"व्यत्यय आणून नकाद्वारे सूचना थांबवल्या"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"आता सुरू करा"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"सूचना नाहीत"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"नवीन सूचना नाहीत"</string>
@@ -1396,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"कोलॅप्स करा आयकन"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"विस्तार करा आयकन"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"किंवा"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"ड्रॅग हॅंडल"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"तुमचा कीबोर्ड वापरून नेव्हिगेट करा"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"कीबोर्ड शॉर्टकट जाणून घ्या"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"तुमचा टचपॅड वापरून नेव्हिगेट करा"</string>
diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml
index 710d342f6ed6..ed3a0bc3d737 100644
--- a/packages/SystemUI/res/values-ms/strings.xml
+++ b/packages/SystemUI/res/values-ms/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"Perbualan"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Kosongkan semua pemberitahuan senyap"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Pemberitahuan dijeda oleh Jangan Ganggu"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"Mulakan sekarang"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"Tiada pemberitahuan"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"Tiada pemberitahuan baharu"</string>
@@ -1396,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Kuncupkan ikon"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Kembangkan ikon"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"atau"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Pemegang seret"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Navigasi menggunakan papan kekunci anda"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Ketahui pintasan papan kekunci"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Navigasi menggunakan pad sentuh anda"</string>
diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml
index 0c9bbe6b9eda..ea10ec267512 100644
--- a/packages/SystemUI/res/values-my/strings.xml
+++ b/packages/SystemUI/res/values-my/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"စကားဝိုင်းများ"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"အသံတိတ် အကြောင်းကြားချက်များအားလုံးကို ရှင်းလင်းရန်"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"အကြောင်းကြားချက်များကို \'မနှောင့်ယှက်ရ\' က ခေတ္တရပ်ထားသည်"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"ယခု စတင်ပါ"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"အကြောင်းကြားချက် မရှိပါ"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"အကြောင်းကြားချက်သစ် မရှိပါ"</string>
@@ -1179,10 +1181,8 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"စပီကာနှင့် ဖန်သားပြင်များ"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"အကြံပြုထားသော စက်ပစ္စည်းများ"</string>
- <!-- no translation found for media_input_group_title (2057057473860783021) -->
- <skip />
- <!-- no translation found for media_output_group_title (6789001895863332576) -->
- <skip />
+ <string name="media_input_group_title" msgid="2057057473860783021">"ထည့်သွင်းချက်"</string>
+ <string name="media_output_group_title" msgid="6789001895863332576">"ထုတ်လုပ်ချက်"</string>
<string name="media_output_end_session_dialog_summary" msgid="5954520685989877347">"အခြားစက်သို့ မီဒီယာရွှေ့ပြောင်းရန် သင်၏မျှဝေထားသောစက်ရှင်ကို ရပ်ပါ"</string>
<string name="media_output_end_session_dialog_stop" msgid="208189434474624412">"ရပ်ရန်"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"ထုတ်လွှင့်မှုဆောင်ရွက်ပုံ"</string>
@@ -1398,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"လျှော့ပြရန် သင်္ကေတ"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"ပိုပြရန် သင်္ကေတ"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"သို့မဟုတ်"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"ဖိဆွဲအထိန်း"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"သင့်ကီးဘုတ်ကိုသုံး၍ လမ်းညွှန်ခြင်း"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"လက်ကွက်ဖြတ်လမ်းများကို လေ့လာပါ"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"သင့်တာ့ချ်ပက်ကိုသုံး၍ လမ်းညွှန်ခြင်း"</string>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index a097505043dc..6ce048e4b673 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"Samtaler"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Fjern alle lydløse varsler"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Varsler er satt på pause av «Ikke forstyrr»"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"Start nå"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"Ingen varsler"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"Ingen nye varsler"</string>
@@ -1179,10 +1181,8 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Høyttalere og skjermer"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Foreslåtte enheter"</string>
- <!-- no translation found for media_input_group_title (2057057473860783021) -->
- <skip />
- <!-- no translation found for media_output_group_title (6789001895863332576) -->
- <skip />
+ <string name="media_input_group_title" msgid="2057057473860783021">"Inngang"</string>
+ <string name="media_output_group_title" msgid="6789001895863332576">"Utgang"</string>
<string name="media_output_end_session_dialog_summary" msgid="5954520685989877347">"Stopp den delte økten for å flytte medieinnholdet til en annen enhet"</string>
<string name="media_output_end_session_dialog_stop" msgid="208189434474624412">"Stopp"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Slik fungerer kringkasting"</string>
@@ -1398,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Skjul-ikon"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Vis-ikon"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"eller"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Håndtak"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Naviger med tastaturet"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Lær deg hurtigtaster"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Naviger med styreflaten"</string>
diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml
index e2ae7b9717ca..01419b45a82e 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"वार्तालापहरू"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"सबै मौन सूचनाहरू हटाउनुहोस्"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"बाधा नपुऱ्याउनुहोस् नामक मोडमार्फत पज पारिएका सूचनाहरू"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"अहिले न"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"कुनै सूचनाहरू छैनन्"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"कुनै पनि नयाँ सूचना छैन"</string>
@@ -1396,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"\"कोल्याप्स गर्नुहोस्\" आइकन"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"\"एक्स्पान्ड गर्नुहोस्\" आइकन"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"वा"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"ड्र्याग ह्यान्डल"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"किबोर्ड प्रयोग गरी नेभिगेट गर्नुहोस्"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"किबोर्डका सर्टकटहरू प्रयोग गर्न सिक्नुहोस्"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"टचप्याड प्रयोग गरी नेभिगेट गर्नुहोस्"</string>
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index 9681f1e94c95..325e361686c5 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"Gesprekken"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Alle stille meldingen wissen"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Meldingen onderbroken door \'Niet storen\'"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"Nu starten"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"Geen meldingen"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"Geen nieuwe meldingen"</string>
@@ -1396,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Icoon voor samenvouwen"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Icoon voor uitvouwen"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"of"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Handgreep voor slepen"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Navigeren met je toetsenbord"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Leer sneltoetsen die je kunt gebruiken"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Navigeren met je touchpad"</string>
diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml
index 14e84901a081..e0349999acde 100644
--- a/packages/SystemUI/res/values-or/strings.xml
+++ b/packages/SystemUI/res/values-or/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"ବାର୍ତ୍ତାଳାପଗୁଡ଼ିକ"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"ସମସ୍ତ ନୀରବ ବିଜ୍ଞପ୍ତିଗୁଡ଼ିକ ଖାଲି କରନ୍ତୁ"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"\"ବିରକ୍ତ କରନ୍ତୁ ନାହିଁ\" ବିକଳ୍ପ ଦ୍ୱାରା ବିଜ୍ଞପ୍ତି ପଜ୍‍ ହୋଇଛି"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"ବର୍ତ୍ତମାନ ଆରମ୍ଭ କରନ୍ତୁ"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"କୌଣସି ବିଜ୍ଞପ୍ତି ନାହିଁ"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"କୌଣସି ନୂଆ ବିଜ୍ଞପ୍ତିଗୁଡ଼ିକ ନାହିଁ"</string>
@@ -1179,10 +1181,8 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"ସ୍ପିକର ଏବଂ ଡିସପ୍ଲେଗୁଡ଼ିକ"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"ପ୍ରସ୍ତାବିତ ଡିଭାଇସଗୁଡ଼ିକ"</string>
- <!-- no translation found for media_input_group_title (2057057473860783021) -->
- <skip />
- <!-- no translation found for media_output_group_title (6789001895863332576) -->
- <skip />
+ <string name="media_input_group_title" msgid="2057057473860783021">"ଇନପୁଟ"</string>
+ <string name="media_output_group_title" msgid="6789001895863332576">"ଆଉଟପୁଟ"</string>
<string name="media_output_end_session_dialog_summary" msgid="5954520685989877347">"ଅନ୍ୟ ଏକ ଡିଭାଇସକୁ ମିଡିଆ ମୁଭ କରିବା ପାଇଁ ଆପଣଙ୍କ ସେୟାର କରାଯାଇଥିବା ସେସନକୁ ବନ୍ଦ କରନ୍ତୁ"</string>
<string name="media_output_end_session_dialog_stop" msgid="208189434474624412">"ବନ୍ଦ କରନ୍ତୁ"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"ବ୍ରଡକାଷ୍ଟିଂ କିପରି କାମ କରେ"</string>
@@ -1398,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"ଆଇକନକୁ ସଙ୍କୁଚିତ କରନ୍ତୁ"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"ଆଇକନକୁ ବିସ୍ତାର କରନ୍ତୁ"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"କିମ୍ବା"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"ଡ୍ରାଗ ହେଣ୍ଡେଲ"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"ଆପଣଙ୍କ କୀବୋର୍ଡ ବ୍ୟବହାର କରି ନାଭିଗେଟ କରନ୍ତୁ"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"କୀବୋର୍ଡ ସର୍ଟକଟଗୁଡ଼ିକ ବିଷୟରେ ଜାଣନ୍ତୁ"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"ଆପଣଙ୍କ ଟଚପେଡ ବ୍ୟବହାର କରି ନାଭିଗେଟ କରନ୍ତୁ"</string>
diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml
index 8e7ed22ec4f5..9659930f23c9 100644
--- a/packages/SystemUI/res/values-pa/strings.xml
+++ b/packages/SystemUI/res/values-pa/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"ਗੱਲਾਂਬਾਤਾਂ"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"ਸਾਰੀਆਂ ਸ਼ਾਂਤ ਸੂਚਨਾਵਾਂ ਕਲੀਅਰ ਕਰੋ"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"\'ਪਰੇਸ਼ਾਨ ਨਾ ਕਰੋ\' ਵੱਲੋਂ ਸੂਚਨਾਵਾਂ ਨੂੰ ਰੋਕਿਆ ਗਿਆ"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"ਹੁਣੇ ਸ਼ੁਰੂ ਕਰੋ"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"ਕੋਈ ਸੂਚਨਾ ਨਹੀਂ"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"ਕੋਈ ਨਵੀਂ ਸੂਚਨਾ ਨਹੀਂ ਹੈ"</string>
@@ -1179,10 +1181,8 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"ਸਪੀਕਰ ਅਤੇ ਡਿਸਪਲੇਆਂ"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"ਸੁਝਾਏ ਗਏ ਡੀਵਾਈਸ"</string>
- <!-- no translation found for media_input_group_title (2057057473860783021) -->
- <skip />
- <!-- no translation found for media_output_group_title (6789001895863332576) -->
- <skip />
+ <string name="media_input_group_title" msgid="2057057473860783021">"ਇਨਪੁੱਟ"</string>
+ <string name="media_output_group_title" msgid="6789001895863332576">"ਆਊਟਪੁੱਟ"</string>
<string name="media_output_end_session_dialog_summary" msgid="5954520685989877347">"ਮੀਡੀਆ ਨੂੰ ਕਿਸੇ ਹੋਰ ਡੀਵਾਈਸ \'ਤੇ ਲਿਜਾਉਣ ਲਈ ਆਪਣੇ ਸਾਂਝੇ ਕੀਤੇ ਸੈਸ਼ਨ ਨੂੰ ਬੰਦ ਕਰੋ"</string>
<string name="media_output_end_session_dialog_stop" msgid="208189434474624412">"ਬੰਦ ਕਰੋ"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"ਪ੍ਰਸਾਰਨ ਕਿਵੇਂ ਕੰਮ ਕਰਦਾ ਹੈ"</string>
@@ -1398,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"ਪ੍ਰਤੀਕ ਨੂੰ ਸਮੇਟੋ"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"ਪ੍ਰਤੀਕ ਦਾ ਵਿਸਤਾਰ ਕਰੋ"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ਜਾਂ"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"ਘਸੀਟਣ ਵਾਲਾ ਹੈਂਡਲ"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"ਆਪਣੇ ਕੀ-ਬੋਰਡ ਦੀ ਵਰਤੋਂ ਕਰ ਕੇ ਨੈਵੀਗੇਟ ਕਰੋ"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"ਕੀ-ਬੋਰਡ ਸ਼ਾਰਟਕੱਟ ਬਾਰੇ ਜਾਣੋ"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"ਆਪਣੇ ਟੱਚਪੈਡ ਦੀ ਵਰਤੋਂ ਕਰ ਕੇ ਨੈਵੀਗੇਟ ਕਰੋ"</string>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index 287ecd37fdf5..9c97423fccc7 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"Rozmowy"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Usuń wszystkie ciche powiadomienia"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Powiadomienia wstrzymane przez tryb Nie przeszkadzać"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"Rozpocznij teraz"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"Brak powiadomień"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"Brak nowych powiadomień"</string>
@@ -1396,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ikona zwijania"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ikona rozwijania"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"lub"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Uchwyt do przeciągania"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Nawiguj za pomocą klawiatury"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Dowiedz się więcej o skrótach klawiszowych"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Nawiguj za pomocą touchpada"</string>
diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml
index 408ffcbac092..5c9679d73d41 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"Conversas"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Apagar todas as notificações silenciosas"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Notificações pausadas pelo modo \"Não perturbe\""</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"Iniciar agora"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"Sem notificações"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"Nenhuma notificação nova"</string>
@@ -1396,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ícone \"Fechar\""</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ícone \"Abrir\""</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ou"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Alça de arrastar"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Navegue usando o teclado"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Aprenda atalhos do teclado"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Navegue usando o touchpad"</string>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index 2e0bb56ab230..e87564d915fc 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"Conversas"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Limpar todas as notificações silenciosas"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Notificações colocadas em pausa pelo modo Não incomodar."</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"Começar agora"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"Sem notificações"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"Não existem novas notificações"</string>
@@ -1179,10 +1181,8 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Altifalantes e ecrãs"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Dispositivos sugeridos"</string>
- <!-- no translation found for media_input_group_title (2057057473860783021) -->
- <skip />
- <!-- no translation found for media_output_group_title (6789001895863332576) -->
- <skip />
+ <string name="media_input_group_title" msgid="2057057473860783021">"Entrada"</string>
+ <string name="media_output_group_title" msgid="6789001895863332576">"Saída"</string>
<string name="media_output_end_session_dialog_summary" msgid="5954520685989877347">"Pare a sua sessão partilhada para mover conteúdos multimédia para outro dispositivo"</string>
<string name="media_output_end_session_dialog_stop" msgid="208189434474624412">"Parar"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Como funciona a transmissão"</string>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index 408ffcbac092..5c9679d73d41 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"Conversas"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Apagar todas as notificações silenciosas"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Notificações pausadas pelo modo \"Não perturbe\""</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"Iniciar agora"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"Sem notificações"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"Nenhuma notificação nova"</string>
@@ -1396,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ícone \"Fechar\""</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ícone \"Abrir\""</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ou"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Alça de arrastar"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Navegue usando o teclado"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Aprenda atalhos do teclado"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Navegue usando o touchpad"</string>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index 443d28aa04ed..8c764910f638 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"Conversații"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Șterge toate notificările silențioase"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Notificări întrerupte prin „Nu deranja”"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"Începe acum"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"Nicio notificare"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"Nicio notificare nouă"</string>
@@ -1179,10 +1181,8 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Difuzoare și ecrane"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Dispozitive sugerate"</string>
- <!-- no translation found for media_input_group_title (2057057473860783021) -->
- <skip />
- <!-- no translation found for media_output_group_title (6789001895863332576) -->
- <skip />
+ <string name="media_input_group_title" msgid="2057057473860783021">"Intrare"</string>
+ <string name="media_output_group_title" msgid="6789001895863332576">"Ieșire"</string>
<string name="media_output_end_session_dialog_summary" msgid="5954520685989877347">"Oprește sesiunea comună ca să muți elementul media pe alt dispozitiv"</string>
<string name="media_output_end_session_dialog_stop" msgid="208189434474624412">"Oprește"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Cum funcționează transmisia"</string>
@@ -1398,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Pictograma de restrângere"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Pictograma de extindere"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"sau"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Ghidaj de tragere"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Navighează folosind tastatura"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Învață comenzile rapide de la tastatură"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Navighează folosind touchpadul"</string>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index 66d78087d792..c5b6479a9b15 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"Разговоры"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Отклонить все беззвучные уведомления"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"В режиме \"Не беспокоить\" уведомления заблокированы"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"Начать"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"Нет уведомлений."</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"Новых уведомлений нет"</string>
@@ -1396,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Значок \"Свернуть\""</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Значок \"Развернуть\""</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"или"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Маркер перемещения"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Навигация с помощью клавиатуры"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Узнайте о сочетаниях клавиш."</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Навигация с помощью сенсорной панели"</string>
diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml
index 7dd43a95ebf1..ff0c28dbbf26 100644
--- a/packages/SystemUI/res/values-si/strings.xml
+++ b/packages/SystemUI/res/values-si/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"සංවාද"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"සියලු නිහඬ දැනුම්දීම් හිස් කරන්න"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"බාධා නොකරන්න මගින් විරාම කරන ලද දැනුම්දීම්"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"දැන් අරඹන්න"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"දැනුම්දීම් නැත"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"නව දැනුම්දීම් නැත"</string>
@@ -1396,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"හැකුළුම් නිරූපකය"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"දිගහැරීම් නිරූපකය"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"හෝ"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"ඇදීම් හැඬලය"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"ඔබේ යතුරු පුවරුව භාවිතයෙන් සංචාලනය කරන්න"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"යතුරුපුවරු කෙටිමං ඉගෙන ගන්න"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"ඔබේ ස්පර්ශ පෑඩ් භාවිතයෙන් සංචාලනය කරන්න"</string>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index a13a1b45f0af..16af11a26b94 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -152,12 +152,12 @@
<string name="cast_to_other_device_stop_dialog_message_generic" msgid="4100272100480415076">"Momentálne prenášate do zariadenia v okolí"</string>
<string name="cast_to_other_device_stop_dialog_button" msgid="6420183747435521834">"Zastaviť prenos"</string>
<string name="close_dialog_button" msgid="4749497706540104133">"Zavrieť"</string>
- <string name="issuerecord_title" msgid="286627115110121849">"Nástroj na zaznamenávanie problémov"</string>
+ <string name="issuerecord_title" msgid="286627115110121849">"Nahrávanie problémov"</string>
<string name="issuerecord_background_processing_label" msgid="1666840264959336876">"Záznam problému sa spracúva"</string>
<string name="issuerecord_channel_description" msgid="6142326363431474632">"Upozornenie na prebiehajúcu aktivitu týkajúcu sa relácie zhromažďovania problémov"</string>
- <string name="issuerecord_ongoing_screen_only" msgid="6248206059935015722">"Problém sa zaznamenáva"</string>
+ <string name="issuerecord_ongoing_screen_only" msgid="6248206059935015722">"Problém sa nahráva"</string>
<string name="issuerecord_share_label" msgid="3992657993619876199">"Zdieľať"</string>
- <string name="issuerecord_save_title" msgid="4161043023696751591">"Záznam problému bol uložený"</string>
+ <string name="issuerecord_save_title" msgid="4161043023696751591">"Nahrávka problému bola uložená"</string>
<string name="issuerecord_save_text" msgid="1205985304551521495">"Zobrazíte ho klepnutím"</string>
<string name="issuerecord_save_error" msgid="6913040083446722726">"Pri ukladaní záznamu problému sa vyskytla chyba"</string>
<string name="issuerecord_start_error" msgid="3402782952722871190">"Pri spúšťaní záznamu problému sa vyskytla chyba"</string>
@@ -380,16 +380,16 @@
<string name="quick_settings_nfc_label" msgid="1054317416221168085">"NFC"</string>
<string name="quick_settings_nfc_off" msgid="3465000058515424663">"NFC je deaktivované"</string>
<string name="quick_settings_nfc_on" msgid="1004976611203202230">"NFC je aktivované"</string>
- <string name="quick_settings_screen_record_label" msgid="8650355346742003694">"Rekordér obrazovky"</string>
+ <string name="quick_settings_screen_record_label" msgid="8650355346742003694">"Nahrávanie obrazovky"</string>
<string name="quick_settings_screen_record_start" msgid="1574725369331638985">"Začať"</string>
<string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"Ukončiť"</string>
- <string name="qs_record_issue_label" msgid="8166290137285529059">"Zaznamenať problém"</string>
+ <string name="qs_record_issue_label" msgid="8166290137285529059">"Nahrať problém"</string>
<string name="qs_record_issue_start" msgid="2979831312582567056">"Začať"</string>
<string name="qs_record_issue_stop" msgid="3531747965741982657">"Zastavte"</string>
<string name="qs_record_issue_bug_report" msgid="8229031766918650079">"Hlásenie chyby"</string>
<string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"Čo v zariadení bolo ovplyvnené?"</string>
<string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"Vyberte typ problému"</string>
- <string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"Rekordér obrazovky"</string>
+ <string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"Nahrávanie obrazovky"</string>
<string name="performance" msgid="6552785217174378320">"Výkon"</string>
<string name="user_interface" msgid="3712869377953950887">"Používateľské rozhranie"</string>
<string name="thermal" msgid="6758074791325414831">"Teplota"</string>
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"Konverzácie"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Vymazať všetky tiché upozornenia"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Upozornenia sú pozastavené režimom bez vyrušení"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"Spustiť"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"Žiadne upozornenia"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"Žiadne nové upozornenia"</string>
@@ -1179,10 +1181,8 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Reproduktory a obrazovky"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Navrhované zariadenia"</string>
- <!-- no translation found for media_input_group_title (2057057473860783021) -->
- <skip />
- <!-- no translation found for media_output_group_title (6789001895863332576) -->
- <skip />
+ <string name="media_input_group_title" msgid="2057057473860783021">"Vstup"</string>
+ <string name="media_output_group_title" msgid="6789001895863332576">"Výstup"</string>
<string name="media_output_end_session_dialog_summary" msgid="5954520685989877347">"Ak chcete preniesť médiá do iného zariadenia, ukončite zdieľanú reláciu"</string>
<string name="media_output_end_session_dialog_stop" msgid="208189434474624412">"Ukončiť"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Ako vysielanie funguje"</string>
@@ -1398,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ikona zbalenia"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ikona rozbalenia"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"alebo"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Presúvadlo"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Prechádzajte pomocou klávesnice"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Naučte sa klávesové skratky"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Prechádzajte pomocou touchpadu"</string>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index 653bb586200d..e664810b82c0 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"Pogovori"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Brisanje vseh tihih obvestil"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Prikazovanje obvestil je začasno zaustavljeno z načinom »ne moti«"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"Začni zdaj"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"Ni obvestil"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"Ni novih obvestil"</string>
diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml
index 00484a7b737d..e70629f20cc9 100644
--- a/packages/SystemUI/res/values-sq/strings.xml
+++ b/packages/SystemUI/res/values-sq/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"Bisedat"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Pastro të gjitha njoftimet në heshtje"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Njoftimet janë vendosur në pauzë nga modaliteti \"Mos shqetëso\""</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"Fillo tani"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"Asnjë njoftim"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"Nuk ka njoftime të reja"</string>
@@ -1179,10 +1181,8 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Altoparlantët dhe ekranet"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Pajisjet e sugjeruara"</string>
- <!-- no translation found for media_input_group_title (2057057473860783021) -->
- <skip />
- <!-- no translation found for media_output_group_title (6789001895863332576) -->
- <skip />
+ <string name="media_input_group_title" msgid="2057057473860783021">"Hyrja"</string>
+ <string name="media_output_group_title" msgid="6789001895863332576">"Dalja"</string>
<string name="media_output_end_session_dialog_summary" msgid="5954520685989877347">"Ndalo sesionin e ndarë për ta zhvendosur median në një pajisje tjetër"</string>
<string name="media_output_end_session_dialog_stop" msgid="208189434474624412">"Ndalo"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Si funksionon transmetimi"</string>
@@ -1398,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ikona e palosjes"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ikona e zgjerimit"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ose"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Doreza e zvarritjes"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Navigo duke përdorur tastierën tënde"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Mëso shkurtoret e tastierës"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Navigo duke përdorur bllokun me prekje"</string>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index 3a940c1155f6..164b4aa246a9 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"Конверзације"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Обришите сва нечујна обавештења"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Обавештења су паузирана режимом Не узнемиравај"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"Започни"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"Нема обавештења"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"Нема нових обавештења"</string>
@@ -1396,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Икона за скупљање"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Икона за проширивање"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"или"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Маркер за превлачење"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Крећите се помоћу тастатуре"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Сазнајте више о тастерским пречицама"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Крећите се помоћу тачпеда"</string>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index 23f476ddad93..3016e83464fd 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"Konversationer"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Rensa alla ljudlösa aviseringar"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Aviseringar har pausats via Stör ej"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"Starta nu"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"Inga aviseringar"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"Det finns inga nya aviseringar"</string>
@@ -1179,10 +1181,8 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g> %%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Högtalare och skärmar"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Förslag på enheter"</string>
- <!-- no translation found for media_input_group_title (2057057473860783021) -->
- <skip />
- <!-- no translation found for media_output_group_title (6789001895863332576) -->
- <skip />
+ <string name="media_input_group_title" msgid="2057057473860783021">"Ingång"</string>
+ <string name="media_output_group_title" msgid="6789001895863332576">"Utgång"</string>
<string name="media_output_end_session_dialog_summary" msgid="5954520685989877347">"Stoppa din delade session för att flytta media till en annan enhet"</string>
<string name="media_output_end_session_dialog_stop" msgid="208189434474624412">"Stoppa"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Så fungerar utsändning"</string>
@@ -1398,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ikonen Komprimera"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ikonen Utöka"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"eller"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Handtag"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Navigera med tangentbordet"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Lär dig kortkommandon"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Navigera med styrplattan"</string>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index dee6a5f49879..45095dfcc0bc 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"Mazungumzo"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Futa arifa zote zisizo na sauti"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Kipengele cha Usinisumbue kimesitisha arifa"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"Anza sasa"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"Hakuna arifa"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"Hakuna arifa mpya"</string>
@@ -1179,10 +1181,8 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Spika na Skrini"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Vifaa Vilivyopendekezwa"</string>
- <!-- no translation found for media_input_group_title (2057057473860783021) -->
- <skip />
- <!-- no translation found for media_output_group_title (6789001895863332576) -->
- <skip />
+ <string name="media_input_group_title" msgid="2057057473860783021">"Vifaa vya kuingiza data"</string>
+ <string name="media_output_group_title" msgid="6789001895863332576">"Vifaa vya kutoa maudhui"</string>
<string name="media_output_end_session_dialog_summary" msgid="5954520685989877347">"Simamisha kipindi unachoshiriki ili uhamishie maudhui kwenye kifaa kingine"</string>
<string name="media_output_end_session_dialog_stop" msgid="208189434474624412">"Simamisha"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Jinsi utangazaji unavyofanya kazi"</string>
@@ -1398,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Kunja aikoni"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Panua aikoni"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"au"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Aikoni ya buruta"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Kusogeza kwa kutumia kibodi yako"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Jifunze kuhusu mikato ya kibodi"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Kusogeza kwa kutumia padi yako ya kugusa"</string>
diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml
index 92da9089a8f4..3dd0c73170fd 100644
--- a/packages/SystemUI/res/values-ta/strings.xml
+++ b/packages/SystemUI/res/values-ta/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"உரையாடல்கள்"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"சைலன்ட் அறிவிப்புகள் அனைத்தையும் அழிக்கும்"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"\'தொந்தரவு செய்ய வேண்டாம்\' அம்சத்தின் மூலம் அறிவிப்புகள் இடைநிறுத்தப்பட்டுள்ளன"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"இப்போது தொடங்கு"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"அறிவிப்புகள் இல்லை"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"புதிய அறிவிப்புகள் இல்லை"</string>
@@ -1179,10 +1181,8 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"ஸ்பீக்கர்கள் &amp; டிஸ்ப்ளேக்கள்"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"பரிந்துரைக்கப்படும் சாதனங்கள்"</string>
- <!-- no translation found for media_input_group_title (2057057473860783021) -->
- <skip />
- <!-- no translation found for media_output_group_title (6789001895863332576) -->
- <skip />
+ <string name="media_input_group_title" msgid="2057057473860783021">"உள்ளீடு"</string>
+ <string name="media_output_group_title" msgid="6789001895863332576">"வெளியீடு"</string>
<string name="media_output_end_session_dialog_summary" msgid="5954520685989877347">"மீடியாவை வேறொரு சாதனத்திற்கு மாற்ற \'பகிரப்படும் அமர்வை\' நிறுத்தவும்"</string>
<string name="media_output_end_session_dialog_stop" msgid="208189434474624412">"நிறுத்து"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"பிராட்காஸ்ட் எவ்வாறு செயல்படுகிறது?"</string>
@@ -1398,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"சுருக்குவதற்கான ஐகான்"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"விரிவாக்குவதற்கான ஐகான்"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"அல்லது"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"இழுப்பதற்கான ஹேண்டில்"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"கீபோர்டைப் பயன்படுத்திச் செல்லுதல்"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"கீபோர்டு ஷார்ட்கட்கள் குறித்துத் தெரிந்துகொள்ளுங்கள்"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"டச்பேடைப் பயன்படுத்திச் செல்லுதல்"</string>
diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml
index 16500ed9d415..65de840488f6 100644
--- a/packages/SystemUI/res/values-te/strings.xml
+++ b/packages/SystemUI/res/values-te/strings.xml
@@ -369,7 +369,7 @@
<string name="quick_settings_night_secondary_label_until_sunrise" msgid="4063448287758262485">"సూర్యోదయం వరకు"</string>
<string name="quick_settings_night_secondary_label_on_at" msgid="3584738542293528235">"<xliff:g id="TIME">%s</xliff:g>కి"</string>
<string name="quick_settings_secondary_label_until" msgid="1883981263191927372">"<xliff:g id="TIME">%s</xliff:g> వరకు"</string>
- <string name="quick_settings_ui_mode_night_label" msgid="1398928270610780470">"ముదురు రంగు రూపం"</string>
+ <string name="quick_settings_ui_mode_night_label" msgid="1398928270610780470">"డార్క్ థీమ్‌"</string>
<string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"బ్యాటరీ సేవర్"</string>
<string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"సూర్యాస్తమయానికి"</string>
<string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"సూర్యోదయం వరకు"</string>
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"సంభాషణలు"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"అన్ని నిశ్శబ్ద నోటిఫికేషన్‌లను క్లియర్ చేస్తుంది"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"అంతరాయం కలిగించవద్దు ద్వారా నోటిఫికేషన్‌లు పాజ్ చేయబడ్డాయి"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"ఇప్పుడే ప్రారంభించండి"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"నోటిఫికేషన్‌లు లేవు"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"కొత్త నోటిఫికేషన్‌లు ఏవీ లేవు"</string>
@@ -1396,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"కుదించండి చిహ్నం"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"విస్తరించండి చిహ్నం"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"లేదా"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"లాగే హ్యాండిల్"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"మీ కీబోర్డ్ ఉపయోగించి నావిగేట్ చేయండి"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"కీబోర్డ్ షార్ట్‌కట్‌ల గురించి తెలుసుకోండి"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"మీ టచ్‌ప్యాడ్‌ని ఉపయోగించి నావిగేట్ చేయండి"</string>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index 6bfc9f9be3d1..b8a385a12fec 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"การสนทนา"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"ล้างการแจ้งเตือนแบบไม่มีเสียงทั้งหมด"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"หยุดการแจ้งเตือนชั่วคราวโดย \"ห้ามรบกวน\""</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"เริ่มเลย"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"ไม่มีการแจ้งเตือน"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"ไม่มีการแจ้งเตือนใหม่"</string>
@@ -965,7 +967,7 @@
<string name="tuner_menu" msgid="363690665924769420">"เมนู"</string>
<string name="tuner_app" msgid="6949280415826686972">"แอป <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="notification_channel_alerts" msgid="3385787053375150046">"การแจ้งเตือน"</string>
- <string name="notification_channel_battery" msgid="9219995638046695106">"แบตเตอรี"</string>
+ <string name="notification_channel_battery" msgid="9219995638046695106">"แบตเตอรี่"</string>
<string name="notification_channel_screenshot" msgid="7665814998932211997">"ภาพหน้าจอ"</string>
<string name="notification_channel_instant" msgid="7556135423486752680">"Instant Apps"</string>
<string name="notification_channel_setup" msgid="7660580986090760350">"ตั้งค่า"</string>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index 4f3819b161ee..f2cecf97b7b1 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"Mga Pag-uusap"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"I-clear ang lahat ng silent na notification"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Mga notification na na-pause ng Huwag Istorbohin"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"Magsimula ngayon"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"Walang mga notification"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"Walang bagong notification"</string>
@@ -1396,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"I-collapse ang icon"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"I-expand ang icon"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"o"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Handle sa pag-drag"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Mag-navigate gamit ang iyong keyboard"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Matuto ng mga keyboard shortcut"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Mag-navigate gamit ang iyong touchpad"</string>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index 19fad73cf2c9..570657d4494a 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"Görüşmeler"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Sessiz bildirimlerin tümünü temizle"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Bildirimler, Rahatsız Etmeyin özelliği tarafından duraklatıldı"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"Şimdi başlat"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"Bildirim yok"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"Yeni bildirim yok"</string>
@@ -1179,10 +1181,8 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"%%<xliff:g id="PERCENTAGE">%1$d</xliff:g>"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Hoparlörler ve Ekranlar"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Önerilen Cihazlar"</string>
- <!-- no translation found for media_input_group_title (2057057473860783021) -->
- <skip />
- <!-- no translation found for media_output_group_title (6789001895863332576) -->
- <skip />
+ <string name="media_input_group_title" msgid="2057057473860783021">"Giriş"</string>
+ <string name="media_output_group_title" msgid="6789001895863332576">"Çıkış"</string>
<string name="media_output_end_session_dialog_summary" msgid="5954520685989877347">"Medyayı başka bir cihaza taşımak için paylaşılan oturumunuzu durdurun"</string>
<string name="media_output_end_session_dialog_stop" msgid="208189434474624412">"Durdur"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Yayınlamanın işleyiş şekli"</string>
@@ -1398,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Daralt simgesi"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Genişlet simgesi"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"veya"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Sürükleme tutamacı"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Klavyenizi kullanarak gezinin"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Klavye kısayollarını öğrenin"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Dokunmatik alanınızı kullanarak gezinin"</string>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index 453ce4a9bb58..65b1b39cccd2 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"Розмови"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Очистити всі беззвучні сповіщення"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Режим \"Не турбувати\" призупинив сповіщення"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"Почати зараз"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"Сповіщень немає"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"Немає нових сповіщень"</string>
@@ -1179,10 +1181,8 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Колонки й екрани"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Пропоновані пристрої"</string>
- <!-- no translation found for media_input_group_title (2057057473860783021) -->
- <skip />
- <!-- no translation found for media_output_group_title (6789001895863332576) -->
- <skip />
+ <string name="media_input_group_title" msgid="2057057473860783021">"Введення"</string>
+ <string name="media_output_group_title" msgid="6789001895863332576">"Виведення"</string>
<string name="media_output_end_session_dialog_summary" msgid="5954520685989877347">"Зупиніть сеанс спільного доступу, щоб перенести медіаконтент на інший пристрій"</string>
<string name="media_output_end_session_dialog_stop" msgid="208189434474624412">"Зупинити"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Як працює трансляція"</string>
@@ -1398,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Значок згортання"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Значок розгортання"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"або"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Маркер переміщення"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Навігація за допомогою клавіатури"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Комбінації клавіш: докладніше"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Навігація за допомогою сенсорної панелі"</string>
diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml
index 9865c8de6290..e65fd98e7dbf 100644
--- a/packages/SystemUI/res/values-ur/strings.xml
+++ b/packages/SystemUI/res/values-ur/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"گفتگوئیں"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"سبھی خاموش اطلاعات کو صاف کریں"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"\'ڈسٹرب نہ کریں\' کے ذریعے اطلاعات کو موقوف کیا گیا"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"ابھی شروع کریں"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"کوئی اطلاعات نہیں ہیں"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"کوئی نئی اطلاعات نہیں"</string>
@@ -1396,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"آئیکن سکیڑیں"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"آئیکن پھیلائیں"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"یا"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"گھسیٹنے کا ہینڈل"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"اپنے کی بورڈ کا استعمال کر کے نیویگیٹ کریں"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"کی بورڈ شارٹ کٹس جانیں"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"اپنے ٹچ پیڈ کا استعمال کر کے نیویگیٹ کریں"</string>
diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml
index cd4cc0dc9681..0f2db3799438 100644
--- a/packages/SystemUI/res/values-uz/strings.xml
+++ b/packages/SystemUI/res/values-uz/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"Suhbatlar"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Barcha sokin bildirishnomalarni tozalash"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Bezovta qilinmasin rejimida bildirishnomalar pauza qilinadi"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"Boshlash"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"Bildirishnomalar yo‘q"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"Yangi bildirishoma yoʻq"</string>
@@ -1179,10 +1181,8 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Karnaylar va displeylar"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Taklif qilingan qurilmalar"</string>
- <!-- no translation found for media_input_group_title (2057057473860783021) -->
- <skip />
- <!-- no translation found for media_output_group_title (6789001895863332576) -->
- <skip />
+ <string name="media_input_group_title" msgid="2057057473860783021">"Kiritish qurilmasi"</string>
+ <string name="media_output_group_title" msgid="6789001895863332576">"Chiqarish qurilmasi"</string>
<string name="media_output_end_session_dialog_summary" msgid="5954520685989877347">"Mediani boshqa qurilmaga koʻchirish uchun umumiy seansingizni toʻxtating"</string>
<string name="media_output_end_session_dialog_stop" msgid="208189434474624412">"Toʻxtatish"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Translatsiya qanday ishlaydi"</string>
@@ -1398,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Yigʻish belgisi"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Yoyish belgisi"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"yoki"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Surish dastagi"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Klaviatura yordamida kezing"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Tezkor tugmalar haqida"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Sensorli panel yordamida kezing"</string>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index 28a6900c1a5e..a37a7cd31343 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"Cuộc trò chuyện"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Xóa tất cả thông báo im lặng"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Chế độ Không làm phiền đã tạm dừng thông báo"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"Bắt đầu ngay"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"Không có thông báo nào"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"Không có thông báo mới"</string>
@@ -1179,10 +1181,8 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Loa và màn hình"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Thiết bị được đề xuất"</string>
- <!-- no translation found for media_input_group_title (2057057473860783021) -->
- <skip />
- <!-- no translation found for media_output_group_title (6789001895863332576) -->
- <skip />
+ <string name="media_input_group_title" msgid="2057057473860783021">"Đầu vào"</string>
+ <string name="media_output_group_title" msgid="6789001895863332576">"Đầu ra"</string>
<string name="media_output_end_session_dialog_summary" msgid="5954520685989877347">"Dừng phiên chia sẻ của bạn để chuyển nội dung nghe nhìn sang thiết bị khác"</string>
<string name="media_output_end_session_dialog_stop" msgid="208189434474624412">"Dừng"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Cách tính năng truyền hoạt động"</string>
@@ -1398,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Biểu tượng Thu gọn"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Biểu tượng Mở rộng"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"hoặc"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Nút kéo"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Di chuyển bằng bàn phím"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Tìm hiểu về phím tắt"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Di chuyển bằng bàn di chuột"</string>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index 267f3923fac5..0d270beb0bf9 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -98,7 +98,7 @@
<string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"左侧边界百分之 <xliff:g id="PERCENT">%1$d</xliff:g>"</string>
<string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"右侧边界百分之 <xliff:g id="PERCENT">%1$d</xliff:g>"</string>
<string name="screenshot_work_profile_notification" msgid="203041724052970693">"已保存到工作资料名下的 <xliff:g id="APP">%1$s</xliff:g>中"</string>
- <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"已保存在私密个人资料中的<xliff:g id="APP">%1$s</xliff:g>内"</string>
+ <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"已保存在私密资料中的“<xliff:g id="APP">%1$s</xliff:g>”内"</string>
<string name="screenshot_default_files_app_name" msgid="8721579578575161912">"文件"</string>
<string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> 检测到此屏幕截图。"</string>
<string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> 及其他打开的应用检测到此屏幕截图。"</string>
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"对话"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"清除所有静音通知"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"勿扰模式暂停的通知"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"立即开始"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"没有通知"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"没有新通知"</string>
@@ -620,7 +622,7 @@
<string name="monitoring_description_personal_profile_named_vpn" msgid="5083909710727365452">"您的个人应用已通过“<xliff:g id="VPN_APP">%1$s</xliff:g>”连接到互联网。您的 VPN 提供商可以查看您的网络活动,包括电子邮件和浏览数据。"</string>
<string name="monitoring_description_vpn_settings_separator" msgid="8292589617720435430">" "</string>
<string name="monitoring_description_vpn_settings" msgid="5264167033247632071">"打开 VPN 设置"</string>
- <string name="monitoring_description_parental_controls" msgid="8184693528917051626">"此设备由您的家长管理。您的家长可以查看和管理相关信息,例如您使用的应用、您的位置信息和设备使用时间。"</string>
+ <string name="monitoring_description_parental_controls" msgid="8184693528917051626">"此设备由您的家长管理。您的家长可以查看和管理相关信息,例如您使用的应用、您的位置信息和屏幕使用时间。"</string>
<string name="legacy_vpn_name" msgid="4174223520162559145">"VPN"</string>
<string name="keyguard_indication_trust_unlocked" msgid="7395154975733744547">"由 TrustAgent 保持解锁状态"</string>
<string name="kg_prompt_after_adaptive_auth_lock" msgid="2587481497846342760">"尝试验证身份的次数过多,设备已锁定"</string>
@@ -1179,10 +1181,8 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"音箱和显示屏"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"建议的设备"</string>
- <!-- no translation found for media_input_group_title (2057057473860783021) -->
- <skip />
- <!-- no translation found for media_output_group_title (6789001895863332576) -->
- <skip />
+ <string name="media_input_group_title" msgid="2057057473860783021">"输入"</string>
+ <string name="media_output_group_title" msgid="6789001895863332576">"输出"</string>
<string name="media_output_end_session_dialog_summary" msgid="5954520685989877347">"停止共享的会话,即可将媒体移到其他设备"</string>
<string name="media_output_end_session_dialog_stop" msgid="208189434474624412">"停止"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"广播的运作方式"</string>
@@ -1398,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"收起图标"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"展开图标"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"或"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"拖动手柄"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"使用键盘导航"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"了解键盘快捷键"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"使用触控板导航"</string>
diff --git a/packages/SystemUI/res/values-zh-rCN/tiles_states_strings.xml b/packages/SystemUI/res/values-zh-rCN/tiles_states_strings.xml
index 0446a1bc538b..7748251b9430 100644
--- a/packages/SystemUI/res/values-zh-rCN/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/tiles_states_strings.xml
@@ -128,8 +128,8 @@
</string-array>
<string-array name="tile_states_record_issue">
<item msgid="1727196795383575383">"不可用"</item>
- <item msgid="9061144428113385092">"已停用"</item>
- <item msgid="2984256114867200368">"已启用"</item>
+ <item msgid="9061144428113385092">"已关闭"</item>
+ <item msgid="2984256114867200368">"已开启"</item>
</string-array>
<string-array name="tile_states_reverse">
<item msgid="3574611556622963971">"不可用"</item>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index c08835a38c60..d9a0877ec4aa 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"對話"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"清除所有靜音通知"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"「請勿騷擾」模式已將通知暫停"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"立即開始"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"沒有通知"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"沒有新通知"</string>
@@ -1179,10 +1181,8 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"喇叭和螢幕"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"建議的裝置"</string>
- <!-- no translation found for media_input_group_title (2057057473860783021) -->
- <skip />
- <!-- no translation found for media_output_group_title (6789001895863332576) -->
- <skip />
+ <string name="media_input_group_title" msgid="2057057473860783021">"輸入"</string>
+ <string name="media_output_group_title" msgid="6789001895863332576">"輸出"</string>
<string name="media_output_end_session_dialog_summary" msgid="5954520685989877347">"停止共享工作階段以移動媒體至其他裝置"</string>
<string name="media_output_end_session_dialog_stop" msgid="208189434474624412">"停止"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"廣播運作方式"</string>
@@ -1398,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"收合圖示"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"展開圖示"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"或"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"拖曳控點"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"使用鍵盤導覽"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"瞭解鍵盤快速鍵"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"使用觸控板導覽"</string>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index 787698ca8912..83b456bab0b4 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"對話"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"清除所有靜音通知"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"「零打擾」模式已將通知設為暫停"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"立即開始"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"沒有通知"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"沒有新通知"</string>
@@ -1179,10 +1181,8 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"喇叭和螢幕"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"建議的裝置"</string>
- <!-- no translation found for media_input_group_title (2057057473860783021) -->
- <skip />
- <!-- no translation found for media_output_group_title (6789001895863332576) -->
- <skip />
+ <string name="media_input_group_title" msgid="2057057473860783021">"輸入"</string>
+ <string name="media_output_group_title" msgid="6789001895863332576">"輸出"</string>
<string name="media_output_end_session_dialog_summary" msgid="5954520685989877347">"停止共用的工作階段,即可將媒體移至其他裝置"</string>
<string name="media_output_end_session_dialog_stop" msgid="208189434474624412">"停止"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"廣播功能的運作方式"</string>
@@ -1398,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"收合圖示"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"展開圖示"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"或"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"拖曳控點"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"使用鍵盤操作"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"學習鍵盤快速鍵"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"使用觸控板操作"</string>
@@ -1421,7 +1420,7 @@
<string name="touchpad_recent_apps_gesture_action_title" msgid="934906836867137906">"查看最近使用的應用程式"</string>
<string name="touchpad_recent_apps_gesture_guidance" msgid="6012057247259983871">"在觸控板上用三指向上滑動並按住。"</string>
<string name="touchpad_recent_apps_gesture_success_title" msgid="8481920554139332593">"太棒了!"</string>
- <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"你已完成「查看最近使用的應用程式」手勢的教學課程。"</string>
+ <string name="touchpad_recent_apps_gesture_success_body" msgid="4334263906697493273">"你已完成「查看最近使用的應用程式」手勢教學課程。"</string>
<string name="tutorial_action_key_title" msgid="2659466586996495447">"快捷操作鍵"</string>
<string name="tutorial_action_key_guidance" msgid="5718948664616999196">"如要存取應用程式,請按下鍵盤上的快捷操作鍵。"</string>
<string name="tutorial_action_key_success_title" msgid="466467860120112933">"恭喜!"</string>
@@ -1446,7 +1445,7 @@
<string name="all_apps_edu_notification_title" msgid="372262997265569063">"使用鍵盤查看所有應用程式"</string>
<string name="all_apps_edu_notification_content" msgid="3255070575694025585">"你隨時可以按下快捷操作鍵。輕觸即可進一步瞭解手勢。"</string>
<string name="accessibility_deprecate_extra_dim_dialog_title" msgid="910988771011857460">"「超暗」已併入亮度滑桿"</string>
- <string name="accessibility_deprecate_extra_dim_dialog_description" msgid="4453123359258743230">"現在只要將亮度調得更低,螢幕就會變得更暗。\n\n這項功能已併入亮度滑桿,因此系統將移除「超暗」捷徑。"</string>
+ <string name="accessibility_deprecate_extra_dim_dialog_description" msgid="4453123359258743230">"現在你可以將亮度調得比以往更低,把螢幕變得更暗。\n\n超暗功能現已併入亮度滑桿,因此系統將移除功能捷徑。"</string>
<string name="accessibility_deprecate_extra_dim_dialog_button" msgid="3947537827396916005">"移除「超暗」捷徑"</string>
<string name="accessibility_deprecate_extra_dim_dialog_toast" msgid="165474092660941104">"「超暗」捷徑已移除"</string>
<string name="qs_edit_mode_category_connectivity" msgid="4559726936546032672">"連線"</string>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index 3205b0ea6c64..34f368388d0e 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -573,6 +573,8 @@
<string name="notification_section_header_conversations" msgid="821834744538345661">"Izingxoxo"</string>
<string name="accessibility_notification_section_header_gentle_clear_all" msgid="6490207897764933919">"Sula zonke izaziso ezithulile"</string>
<string name="dnd_suppressing_shade_text" msgid="5588252250634464042">"Izaziso zimiswe okwesikhashana ukungaphazamisi"</string>
+ <!-- no translation found for modes_suppressing_shade_text (6037581130837903239) -->
+ <skip />
<string name="media_projection_action_text" msgid="3634906766918186440">"Qala manje"</string>
<string name="empty_shade_text" msgid="8935967157319717412">"Azikho izaziso"</string>
<string name="no_unseen_notif_text" msgid="395512586119868682">"Azikho izaziso ezintsha"</string>
@@ -1179,10 +1181,8 @@
<string name="media_output_dialog_volume_percentage" msgid="1613984910585111798">"<xliff:g id="PERCENTAGE">%1$d</xliff:g>%%"</string>
<string name="media_output_group_title_speakers_and_displays" msgid="7169712332365659820">"Izipikha Neziboniso"</string>
<string name="media_output_group_title_suggested_device" msgid="4157186235837903826">"Amadivayisi Aphakanyisiwe"</string>
- <!-- no translation found for media_input_group_title (2057057473860783021) -->
- <skip />
- <!-- no translation found for media_output_group_title (6789001895863332576) -->
- <skip />
+ <string name="media_input_group_title" msgid="2057057473860783021">"Okufakwayo"</string>
+ <string name="media_output_group_title" msgid="6789001895863332576">"Umphumela"</string>
<string name="media_output_end_session_dialog_summary" msgid="5954520685989877347">"Misa iseshini yakho eyabiwe ukuze uhambise imidiya kwenye idivayisi"</string>
<string name="media_output_end_session_dialog_stop" msgid="208189434474624412">"Misa"</string>
<string name="media_output_first_broadcast_title" msgid="6292237789860753022">"Indlela ukusakaza okusebenza ngayo"</string>
@@ -1398,8 +1398,7 @@
<string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Goqa isithonjana"</string>
<string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Nweba isithonjana"</string>
<string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"noma"</string>
- <!-- no translation found for shortcut_helper_content_description_drag_handle (5092426406009848110) -->
- <skip />
+ <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Hudula isibambi"</string>
<string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Funa usebenzisa ikhibhodi yakho"</string>
<string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Funda izinqamuleli zamakhibhodi"</string>
<string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Funa usebenzisa iphedi yokuthinta"</string>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index e94248dc72ce..629c94f9a044 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -2047,4 +2047,6 @@
<!-- SliceView icon size -->
<dimen name="abc_slice_big_pic_min_height">64dp</dimen>
<dimen name="abc_slice_big_pic_max_height">64dp</dimen>
+
+ <dimen name="contextual_edu_dialog_bottom_margin">70dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 75389b136a3b..c76b35f0cf9b 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3818,6 +3818,8 @@ Action + ESC for this.</string>
<!-- Main text of the one line view of a redacted notification -->
<string name="redacted_notification_single_line_text">Unlock to view</string>
+ <!-- Content description for contextual education dialog [CHAR LIMIT=NONE] -->
+ <string name="contextual_education_dialog_title">Contextual education</string>
<!-- Education notification title for Back [CHAR_LIMIT=100] -->
<string name="back_edu_notification_title">Use your touchpad to go back</string>
<!-- Education notification text for Back [CHAR_LIMIT=100] -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index a02c35461031..b34d6e4067b6 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -1720,4 +1720,10 @@
<style name="ShortcutHelperTheme" parent="@style/ShortcutHelperThemeCommon">
<item name="android:windowLightNavigationBar">true</item>
</style>
+
+ <style name="ContextualEduDialog" parent="@android:style/Theme.DeviceDefault.Dialog.NoActionBar">
+ <!-- To make the dialog wrap to content when the education text is short -->
+ <item name="windowMinWidthMajor">0%</item>
+ <item name="windowMinWidthMinor">0%</item>
+ </style>
</resources>
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index 8f55961af4e9..0f1da509468a 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -70,6 +70,7 @@ android_library {
"jsr330",
"//frameworks/libs/systemui:com_android_systemui_shared_flags_lib",
"//frameworks/libs/systemui:msdl",
+ "//frameworks/libs/systemui:view_capture",
],
resource_dirs: [
"res",
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java
index f358ba2d3ccd..4db6ab6ea579 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java
@@ -16,6 +16,9 @@
package com.android.systemui.shared.rotation;
+import static com.android.app.viewcapture.ViewCaptureFactory.getViewCaptureAwareWindowManagerInstance;
+import static com.android.systemui.Flags.enableViewCaptureTracing;
+
import android.annotation.DimenRes;
import android.annotation.IdRes;
import android.annotation.LayoutRes;
@@ -30,7 +33,6 @@ import android.graphics.drawable.Drawable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.widget.FrameLayout;
@@ -38,6 +40,7 @@ import android.widget.FrameLayout;
import androidx.annotation.BoolRes;
import androidx.core.view.OneShotPreDrawListener;
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.systemui.shared.rotation.FloatingRotationButtonPositionCalculator.Position;
/**
@@ -47,7 +50,7 @@ public class FloatingRotationButton implements RotationButton {
private static final int MARGIN_ANIMATION_DURATION_MILLIS = 300;
- private final WindowManager mWindowManager;
+ private final ViewCaptureAwareWindowManager mWindowManager;
private final ViewGroup mKeyButtonContainer;
private final FloatingRotationButtonView mKeyButtonView;
@@ -88,7 +91,8 @@ public class FloatingRotationButton implements RotationButton {
@DimenRes int taskbarBottomMargin, @DimenRes int buttonDiameter,
@DimenRes int rippleMaxWidth, @BoolRes int floatingRotationBtnPositionLeftResource) {
mContext = context;
- mWindowManager = mContext.getSystemService(WindowManager.class);
+ mWindowManager = getViewCaptureAwareWindowManagerInstance(mContext,
+ enableViewCaptureTracing());
mKeyButtonContainer = (ViewGroup) LayoutInflater.from(mContext).inflate(layout, null);
mKeyButtonView = mKeyButtonContainer.findViewById(keyButtonId);
mKeyButtonView.setVisibility(View.VISIBLE);
diff --git a/packages/SystemUI/src/com/android/systemui/CoreStartable.java b/packages/SystemUI/src/com/android/systemui/CoreStartable.java
index 55ccaa68c855..92bc95af5931 100644
--- a/packages/SystemUI/src/com/android/systemui/CoreStartable.java
+++ b/packages/SystemUI/src/com/android/systemui/CoreStartable.java
@@ -70,4 +70,12 @@ public interface CoreStartable extends Dumpable {
* {@link #onBootCompleted()} will never be called before {@link #start()}. */
default void onBootCompleted() {
}
+
+ /** No op implementation that can be used when feature flagging on the Dagger Module level. */
+ CoreStartable NOP = new Nop();
+
+ class Nop implements CoreStartable {
+ @Override
+ public void start() {}
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java
index 76df9c96c801..fb00d6e16dcc 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java
@@ -75,6 +75,9 @@ import javax.inject.Named;
* touches are consumed.
*/
public class TouchMonitor {
+ // An incrementing id used to identify the touch monitor instance.
+ private static int sNextInstanceId = 0;
+
private final Logger mLogger;
// This executor is used to protect {@code mActiveTouchSessions} from being modified
// concurrently. Any operation that adds or removes values should use this executor.
@@ -138,7 +141,7 @@ public class TouchMonitor {
completer.set(predecessor);
}
- if (mActiveTouchSessions.isEmpty()) {
+ if (mActiveTouchSessions.isEmpty() && mInitialized) {
if (mStopMonitoringPending) {
stopMonitoring(false);
} else {
@@ -271,7 +274,7 @@ public class TouchMonitor {
@Override
public void onDestroy(LifecycleOwner owner) {
- stopMonitoring(true);
+ destroy();
}
};
@@ -279,6 +282,11 @@ public class TouchMonitor {
* When invoked, instantiates a new {@link InputSession} to monitor touch events.
*/
private void startMonitoring() {
+ if (!mInitialized) {
+ mLogger.w("attempting to startMonitoring when not initialized");
+ return;
+ }
+
mLogger.i("startMonitoring(): monitoring started");
stopMonitoring(true);
@@ -587,7 +595,7 @@ public class TouchMonitor {
mDisplayHelper = displayHelper;
mWindowManagerService = windowManagerService;
mConfigurationInteractor = configurationInteractor;
- mLoggingName = loggingName + ":TouchMonitor";
+ mLoggingName = loggingName + ":TouchMonitor[" + sNextInstanceId++ + "]";
mLogger = new Logger(logBuffer, mLoggingName);
}
@@ -613,7 +621,8 @@ public class TouchMonitor {
*/
public void destroy() {
if (!mInitialized) {
- throw new IllegalStateException("TouchMonitor not initialized");
+ // In the case that we've already been destroyed, this is a no-op
+ return;
}
stopMonitoring(true);
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerHapticPlayer.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerHapticPlayer.kt
index b8c30fe9d4a8..d6b92115c64b 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerHapticPlayer.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerHapticPlayer.kt
@@ -69,7 +69,7 @@ class BouncerHapticPlayer @Inject constructor(private val msdlPlayer: dagger.Laz
HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING,
)
} else {
- msdlPlayer.get().playToken(MSDLToken.DRAG_INDICATOR)
+ msdlPlayer.get().playToken(MSDLToken.DRAG_INDICATOR_DISCRETE)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModel.kt
new file mode 100644
index 000000000000..e35fdfe9087c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModel.kt
@@ -0,0 +1,83 @@
+/*
+ * 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.communal.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.deviceentry.domain.interactor.DeviceUnlockedInteractor
+import com.android.systemui.scene.shared.model.SceneFamilies
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.ui.viewmodel.UserActionsViewModel
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.shared.model.ShadeMode
+import com.android.systemui.shade.ui.viewmodel.dualShadeActions
+import com.android.systemui.shade.ui.viewmodel.singleShadeActions
+import com.android.systemui.shade.ui.viewmodel.splitShadeActions
+import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+
+/** Provides scene container user actions and results. */
+class CommunalUserActionsViewModel
+@AssistedInject
+constructor(
+ private val deviceUnlockedInteractor: DeviceUnlockedInteractor,
+ private val shadeInteractor: ShadeInteractor,
+) : UserActionsViewModel() {
+
+ override suspend fun hydrateActions(setActions: (Map<UserAction, UserActionResult>) -> Unit) {
+ shadeInteractor.isShadeTouchable
+ .flatMapLatestConflated { isShadeTouchable ->
+ if (!isShadeTouchable) {
+ flowOf(emptyMap())
+ } else {
+ combine(
+ deviceUnlockedInteractor.deviceUnlockStatus.map { it.isUnlocked },
+ shadeInteractor.shadeMode,
+ ) { isDeviceUnlocked, shadeMode ->
+ buildList {
+ val bouncerOrGone =
+ if (isDeviceUnlocked) Scenes.Gone else Scenes.Bouncer
+ add(Swipe.Up to bouncerOrGone)
+
+ // "Home" is either Lockscreen, or Gone - if the device is entered.
+ add(Swipe.End to SceneFamilies.Home)
+
+ addAll(
+ when (shadeMode) {
+ ShadeMode.Single -> singleShadeActions()
+ ShadeMode.Split -> splitShadeActions()
+ ShadeMode.Dual -> dualShadeActions()
+ }
+ )
+ }
+ .associate { it }
+ }
+ }
+ }
+ .collect { setActions(it) }
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(): CommunalUserActionsViewModel
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index cbea87676d3a..8da4d460b7a5 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -30,7 +30,7 @@ import com.android.systemui.dreams.AssistantAttentionMonitor
import com.android.systemui.dreams.DreamMonitor
import com.android.systemui.dreams.homecontrols.HomeControlsDreamStartable
import com.android.systemui.globalactions.GlobalActionsComponent
-import com.android.systemui.inputdevice.tutorial.KeyboardTouchpadTutorialCoreStartable
+import com.android.systemui.haptics.msdl.MSDLCoreStartable
import com.android.systemui.keyboard.KeyboardUI
import com.android.systemui.keyboard.PhysicalKeyboardCoreStartable
import com.android.systemui.keyguard.KeyguardViewConfigurator
@@ -323,4 +323,9 @@ abstract class SystemUICoreStartableModule {
@IntoMap
@ClassKey(BatteryControllerStartable::class)
abstract fun bindsBatteryControllerStartable(impl: BatteryControllerStartable): CoreStartable
+
+ @Binds
+ @IntoMap
+ @ClassKey(MSDLCoreStartable::class)
+ abstract fun bindMSDLCoreStartable(impl: MSDLCoreStartable): CoreStartable
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 113e0011f5bd..83f86a718029 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -65,6 +65,7 @@ import com.android.systemui.dreams.dagger.DreamOverlayComponent;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.navigationbar.gestural.domain.GestureInteractor;
import com.android.systemui.navigationbar.gestural.domain.TaskMatcher;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.shade.ShadeExpansionChangeEvent;
import com.android.systemui.touch.TouchInsetManager;
import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -499,8 +500,11 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
mDreamOverlayContainerViewController =
dreamOverlayComponent.getDreamOverlayContainerViewController();
- mTouchMonitor = ambientTouchComponent.getTouchMonitor();
- mTouchMonitor.init();
+
+ if (!SceneContainerFlag.isEnabled()) {
+ mTouchMonitor = ambientTouchComponent.getTouchMonitor();
+ mTouchMonitor.init();
+ }
mStateController.setShouldShowComplications(shouldShowComplications());
diff --git a/packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduDialog.kt b/packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduDialog.kt
new file mode 100644
index 000000000000..287e85ca4358
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduDialog.kt
@@ -0,0 +1,64 @@
+/*
+ * 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.systemui.education.ui.view
+
+import android.app.AlertDialog
+import android.content.Context
+import android.os.Bundle
+import android.view.Gravity
+import android.view.WindowManager
+import android.widget.ToastPresenter
+import com.android.systemui.education.ui.viewmodel.ContextualEduToastViewModel
+import com.android.systemui.res.R
+
+class ContextualEduDialog(context: Context, private val model: ContextualEduToastViewModel) :
+ AlertDialog(context, R.style.ContextualEduDialog) {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ setUpWindowProperties()
+ setWindowPosition()
+ // title is used for a11y announcement
+ window?.setTitle(context.getString(R.string.contextual_education_dialog_title))
+ // TODO: b/369791926 - replace the below toast view with a custom dialog view
+ val toastView = ToastPresenter.getTextToastView(context, model.message)
+ setView(toastView)
+ super.onCreate(savedInstanceState)
+ }
+
+ private fun setUpWindowProperties() {
+ window?.apply {
+ setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG)
+ clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
+ }
+ setCanceledOnTouchOutside(false)
+ }
+
+ private fun setWindowPosition() {
+ window?.apply {
+ setGravity(Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL)
+ this.attributes =
+ WindowManager.LayoutParams().apply {
+ width = WindowManager.LayoutParams.WRAP_CONTENT
+ height = WindowManager.LayoutParams.WRAP_CONTENT
+ copyFrom(attributes)
+ y =
+ context.resources.getDimensionPixelSize(
+ R.dimen.contextual_edu_dialog_bottom_margin
+ )
+ }
+ }
+ }
+}
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 e62b26bfe53d..913ecdd4aadc 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
@@ -16,6 +16,7 @@
package com.android.systemui.education.ui.view
+import android.app.Dialog
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
@@ -24,7 +25,6 @@ import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.os.UserHandle
-import android.widget.Toast
import androidx.core.app.NotificationCompat
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
@@ -49,7 +49,7 @@ constructor(
private val viewModel: ContextualEduViewModel,
private val context: Context,
private val notificationManager: NotificationManager,
- private val createToast: (String) -> Toast
+ private val createDialog: (ContextualEduToastViewModel) -> Dialog,
) : CoreStartable {
companion object {
@@ -69,16 +69,23 @@ constructor(
viewModel,
context,
notificationManager,
- createToast = { message -> Toast.makeText(context, message, Toast.LENGTH_LONG) }
+ createDialog = { model -> ContextualEduDialog(context, model) },
)
+ var dialog: Dialog? = null
+
override fun start() {
createEduNotificationChannel()
applicationScope.launch {
viewModel.eduContent.collect { contentModel ->
- when (contentModel) {
- is ContextualEduToastViewModel -> showToast(contentModel)
- is ContextualEduNotificationViewModel -> showNotification(contentModel)
+ if (contentModel != null) {
+ when (contentModel) {
+ is ContextualEduToastViewModel -> showDialog(contentModel)
+ is ContextualEduNotificationViewModel -> showNotification(contentModel)
+ }
+ } else {
+ dialog?.dismiss()
+ dialog = null
}
}
}
@@ -95,9 +102,9 @@ constructor(
notificationManager.createNotificationChannel(channel)
}
- private fun showToast(model: ContextualEduToastViewModel) {
- val toast = createToast(model.message)
- toast.show()
+ private fun showDialog(model: ContextualEduToastViewModel) {
+ dialog = createDialog(model)
+ dialog?.show()
}
private fun showNotification(model: ContextualEduNotificationViewModel) {
diff --git a/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduViewModel.kt b/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduViewModel.kt
index cd4a8ad8dbda..32e7f41f36b8 100644
--- a/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduViewModel.kt
@@ -17,6 +17,7 @@
package com.android.systemui.education.ui.viewmodel
import android.content.res.Resources
+import android.view.accessibility.AccessibilityManager
import com.android.systemui.contextualeducation.GestureType.ALL_APPS
import com.android.systemui.contextualeducation.GestureType.BACK
import com.android.systemui.contextualeducation.GestureType.HOME
@@ -27,23 +28,63 @@ import com.android.systemui.education.domain.interactor.KeyboardTouchpadEduInter
import com.android.systemui.education.shared.model.EducationInfo
import com.android.systemui.education.shared.model.EducationUiType
import com.android.systemui.res.R
+import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper
import javax.inject.Inject
+import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
+@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
@SysUISingleton
class ContextualEduViewModel
@Inject
-constructor(@Main private val resources: Resources, interactor: KeyboardTouchpadEduInteractor) {
- val eduContent: Flow<ContextualEduContentViewModel> =
- interactor.educationTriggered.filterNotNull().map {
- if (it.educationUiType == EducationUiType.Notification) {
- ContextualEduNotificationViewModel(getEduTitle(it), getEduContent(it), it.userId)
- } else {
- ContextualEduToastViewModel(getEduContent(it), it.userId)
+constructor(
+ @Main private val resources: Resources,
+ interactor: KeyboardTouchpadEduInteractor,
+ private val accessibilityManagerWrapper: AccessibilityManagerWrapper,
+) {
+
+ companion object {
+ const val DEFAULT_DIALOG_TIMEOUT_MILLIS = 3500
+ }
+
+ private val timeoutMillis: Long
+ get() =
+ accessibilityManagerWrapper
+ .getRecommendedTimeoutMillis(
+ DEFAULT_DIALOG_TIMEOUT_MILLIS,
+ AccessibilityManager.FLAG_CONTENT_TEXT,
+ )
+ .toLong()
+
+ val eduContent: Flow<ContextualEduContentViewModel?> =
+ interactor.educationTriggered
+ .filterNotNull()
+ .map {
+ if (it.educationUiType == EducationUiType.Notification) {
+ ContextualEduNotificationViewModel(
+ getEduTitle(it),
+ getEduContent(it),
+ it.userId,
+ )
+ } else {
+ ContextualEduToastViewModel(getEduContent(it), it.userId)
+ }
+ }
+ .timeout(timeoutMillis, emitAfterTimeout = null)
+
+ private fun <T> Flow<T>.timeout(timeoutMillis: Long, emitAfterTimeout: T): Flow<T> {
+ return flatMapLatest {
+ flow {
+ emit(it)
+ delay(timeoutMillis)
+ emit(emitAfterTimeout)
}
}
+ }
private fun getEduContent(educationInfo: EducationInfo): String {
val resourceId =
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
index 820c102e81d8..47f0ecfb237a 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
@@ -34,12 +34,14 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.MigrateClocksToBlueprint
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.shared.flag.DualShade
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.statusbar.core.StatusBarSimpleFragment
import com.android.systemui.statusbar.notification.collection.SortBySectionTimeFlag
import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionRefactor
import com.android.systemui.statusbar.notification.shared.NotificationAvalancheSuppression
-import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype
+import com.android.systemui.statusbar.notification.shared.NotificationMinimalism
import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
import com.android.systemui.statusbar.notification.shared.PriorityPeopleSection
import javax.inject.Inject
@@ -57,7 +59,7 @@ class FlagDependencies @Inject constructor(featureFlags: FeatureFlagsClassic, ha
// Internal notification frontend dependencies
NotificationAvalancheSuppression.token dependsOn VisualInterruptionRefactor.token
PriorityPeopleSection.token dependsOn SortBySectionTimeFlag.token
- NotificationMinimalismPrototype.token dependsOn NotificationThrottleHun.token
+ NotificationMinimalism.token dependsOn NotificationThrottleHun.token
ModesEmptyShadeFix.token dependsOn FooterViewRefactor.token
ModesEmptyShadeFix.token dependsOn modesUi
@@ -73,6 +75,8 @@ class FlagDependencies @Inject constructor(featureFlags: FeatureFlagsClassic, ha
// Status bar chip dependencies
statusBarCallChipNotificationIconToken dependsOn statusBarUseReposForCallChipToken
statusBarCallChipNotificationIconToken dependsOn statusBarScreenSharingChipsToken
+
+ StatusBarConnectedDisplays.token dependsOn StatusBarSimpleFragment.token
}
private inline val politeNotifications
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index 493afde96aff..aa1873c7ad41 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -2298,9 +2298,11 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
}
@Override
- public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
+ public boolean onScroll(@Nullable MotionEvent e1, MotionEvent e2,
+ float distanceX,
float distanceY) {
if (distanceY < 0 && distanceY > distanceX
+ && e1 != null
&& e1.getY() <= mStatusBarWindowController.getStatusBarHeight()) {
// Downwards scroll from top
openShadeAndDismiss();
@@ -2310,9 +2312,11 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
}
@Override
- public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
+ public boolean onFling(@Nullable MotionEvent e1, MotionEvent e2,
+ float velocityX,
float velocityY) {
if (velocityY > 0 && Math.abs(velocityY) > Math.abs(velocityX)
+ && e1 != null
&& e1.getY() <= mStatusBarWindowController.getStatusBarHeight()) {
// Downwards fling from top
openShadeAndDismiss();
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/msdl/MSDLCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/haptics/msdl/MSDLCoreStartable.kt
new file mode 100644
index 000000000000..58736c608af3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/haptics/msdl/MSDLCoreStartable.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.haptics.msdl
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.google.android.msdl.domain.MSDLPlayer
+import com.google.android.msdl.logging.MSDLHistoryLogger
+import java.io.PrintWriter
+import javax.inject.Inject
+
+@SysUISingleton
+class MSDLCoreStartable @Inject constructor(private val msdlPlayer: MSDLPlayer) : CoreStartable {
+ override fun start() {}
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.println("MSDLPlayer history of the last ${MSDLHistoryLogger.HISTORY_SIZE} events:")
+ msdlPlayer.getHistory().forEach { event -> pw.println("$event") }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialMetricsLogger.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialMetricsLogger.kt
new file mode 100644
index 000000000000..144c5ead1bb8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialMetricsLogger.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.systemui.inputdevice.tutorial
+
+import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_ENTRY_POINT_CONTEXTUAL_EDU
+import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_ENTRY_POINT_SCHEDULER
+import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_TYPE_KEYBOARD
+import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_TYPE_TOUCHPAD
+import com.android.systemui.shared.system.SysUiStatsLog
+import javax.inject.Inject
+
+class KeyboardTouchpadTutorialMetricsLogger @Inject constructor() {
+
+ fun logPeripheralTutorialLaunched(entryPointExtra: String?, tutorialTypeExtra: String?) {
+ val entryPoint =
+ when (entryPointExtra) {
+ INTENT_TUTORIAL_ENTRY_POINT_SCHEDULER ->
+ SysUiStatsLog.PERIPHERAL_TUTORIAL_LAUNCHED__ENTRY_POINT__SCHEDULED
+ INTENT_TUTORIAL_ENTRY_POINT_CONTEXTUAL_EDU ->
+ SysUiStatsLog.PERIPHERAL_TUTORIAL_LAUNCHED__ENTRY_POINT__CONTEXTUAL_EDU
+ else -> SysUiStatsLog.PERIPHERAL_TUTORIAL_LAUNCHED__ENTRY_POINT__APP
+ }
+
+ val tutorialType =
+ when (tutorialTypeExtra) {
+ INTENT_TUTORIAL_TYPE_KEYBOARD ->
+ SysUiStatsLog.PERIPHERAL_TUTORIAL_LAUNCHED__TUTORIAL_TYPE__KEYBOARD
+ INTENT_TUTORIAL_TYPE_TOUCHPAD ->
+ SysUiStatsLog.PERIPHERAL_TUTORIAL_LAUNCHED__TUTORIAL_TYPE__TOUCHPAD
+ else -> SysUiStatsLog.PERIPHERAL_TUTORIAL_LAUNCHED__TUTORIAL_TYPE__BOTH
+ }
+
+ SysUiStatsLog.write(SysUiStatsLog.PERIPHERAL_TUTORIAL_LAUNCHED, entryPoint, tutorialType)
+ }
+
+ fun logPeripheralTutorialLaunchedFromSettings() {
+ val entryPoint = SysUiStatsLog.PERIPHERAL_TUTORIAL_LAUNCHED__ENTRY_POINT__SETTINGS
+ val tutorialType = SysUiStatsLog.PERIPHERAL_TUTORIAL_LAUNCHED__TUTORIAL_TYPE__TOUCHPAD
+ SysUiStatsLog.write(SysUiStatsLog.PERIPHERAL_TUTORIAL_LAUNCHED, entryPoint, tutorialType)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/TutorialNotificationCoordinator.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/TutorialNotificationCoordinator.kt
index 5d9dda3899cd..f2afaee1870b 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/TutorialNotificationCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/TutorialNotificationCoordinator.kt
@@ -31,6 +31,8 @@ import com.android.systemui.inputdevice.tutorial.domain.interactor.TutorialSched
import com.android.systemui.inputdevice.tutorial.domain.interactor.TutorialSchedulerInteractor.Companion.TAG
import com.android.systemui.inputdevice.tutorial.domain.interactor.TutorialSchedulerInteractor.TutorialType
import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity
+import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_ENTRY_POINT_KEY
+import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_ENTRY_POINT_SCHEDULER
import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_TYPE_BOTH
import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_TYPE_KEY
import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_TYPE_KEYBOARD
@@ -48,7 +50,7 @@ constructor(
@Background private val backgroundScope: CoroutineScope,
@Application private val context: Context,
private val tutorialSchedulerInteractor: TutorialSchedulerInteractor,
- private val notificationManager: NotificationManager
+ private val notificationManager: NotificationManager,
) {
fun start() {
backgroundScope.launch {
@@ -68,7 +70,7 @@ constructor(
val extras = Bundle()
extras.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 info = getNotificationInfo(tutorialType)!!
@@ -91,7 +93,7 @@ constructor(
NotificationChannel(
CHANNEL_ID,
context.getString(com.android.internal.R.string.android_system_label),
- NotificationManager.IMPORTANCE_DEFAULT
+ NotificationManager.IMPORTANCE_DEFAULT,
)
notificationManager.createNotificationChannel(channel)
}
@@ -100,13 +102,14 @@ constructor(
val intent =
Intent(context, KeyboardTouchpadTutorialActivity::class.java).apply {
putExtra(INTENT_TUTORIAL_TYPE_KEY, tutorialType)
+ putExtra(INTENT_TUTORIAL_ENTRY_POINT_KEY, INTENT_TUTORIAL_ENTRY_POINT_SCHEDULER)
flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
return PendingIntent.getActivity(
context,
/* requestCode= */ 0,
intent,
- PendingIntent.FLAG_IMMUTABLE
+ PendingIntent.FLAG_IMMUTABLE,
)
}
@@ -118,13 +121,13 @@ constructor(
NotificationInfo(
context.getString(R.string.launch_keyboard_tutorial_notification_title),
context.getString(R.string.launch_keyboard_tutorial_notification_content),
- INTENT_TUTORIAL_TYPE_KEYBOARD
+ INTENT_TUTORIAL_TYPE_KEYBOARD,
)
TutorialType.TOUCHPAD ->
NotificationInfo(
context.getString(R.string.launch_touchpad_tutorial_notification_title),
context.getString(R.string.launch_touchpad_tutorial_notification_content),
- INTENT_TUTORIAL_TYPE_TOUCHPAD
+ INTENT_TUTORIAL_TYPE_TOUCHPAD,
)
TutorialType.BOTH ->
NotificationInfo(
@@ -134,7 +137,7 @@ constructor(
context.getString(
R.string.launch_keyboard_touchpad_tutorial_notification_content
),
- INTENT_TUTORIAL_TYPE_BOTH
+ INTENT_TUTORIAL_TYPE_BOTH,
)
TutorialType.NONE -> null
}
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/view/KeyboardTouchpadTutorialActivity.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/view/KeyboardTouchpadTutorialActivity.kt
index c130c6c7fe12..29febd32e925 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/view/KeyboardTouchpadTutorialActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/view/KeyboardTouchpadTutorialActivity.kt
@@ -30,6 +30,7 @@ import androidx.lifecycle.lifecycleScope
import com.android.compose.theme.PlatformTheme
import com.android.systemui.inputdevice.tutorial.InputDeviceTutorialLogger
import com.android.systemui.inputdevice.tutorial.InputDeviceTutorialLogger.TutorialContext
+import com.android.systemui.inputdevice.tutorial.KeyboardTouchpadTutorialMetricsLogger
import com.android.systemui.inputdevice.tutorial.TouchpadTutorialScreensProvider
import com.android.systemui.inputdevice.tutorial.ui.composable.ActionKeyTutorialScreen
import com.android.systemui.inputdevice.tutorial.ui.viewmodel.KeyboardTouchpadTutorialViewModel
@@ -51,6 +52,7 @@ constructor(
private val viewModelFactoryAssistedProvider: ViewModelFactoryAssistedProvider,
private val touchpadTutorialScreensProvider: Optional<TouchpadTutorialScreensProvider>,
private val logger: InputDeviceTutorialLogger,
+ private val metricsLogger: KeyboardTouchpadTutorialMetricsLogger,
) : ComponentActivity() {
companion object {
@@ -58,6 +60,9 @@ constructor(
const val INTENT_TUTORIAL_TYPE_TOUCHPAD = "touchpad"
const val INTENT_TUTORIAL_TYPE_KEYBOARD = "keyboard"
const val INTENT_TUTORIAL_TYPE_BOTH = "both"
+ const val INTENT_TUTORIAL_ENTRY_POINT_KEY = "entry_point"
+ const val INTENT_TUTORIAL_ENTRY_POINT_SCHEDULER = "scheduler"
+ const val INTENT_TUTORIAL_ENTRY_POINT_CONTEXTUAL_EDU = "contextual_edu"
}
private val vm by
@@ -86,6 +91,10 @@ constructor(
PlatformTheme { KeyboardTouchpadTutorialContainer(vm, touchpadTutorialScreensProvider) }
}
if (savedInstanceState == null) {
+ metricsLogger.logPeripheralTutorialLaunched(
+ intent.getStringExtra(INTENT_TUTORIAL_ENTRY_POINT_KEY),
+ intent.getStringExtra(INTENT_TUTORIAL_TYPE_KEY),
+ )
logger.logOpenTutorial(TutorialContext.KEYBOARD_TOUCHPAD_TUTORIAL)
}
}
@@ -109,7 +118,7 @@ fun KeyboardTouchpadTutorialContainer(
ACTION_KEY ->
ActionKeyTutorialScreen(
onDoneButtonClicked = vm::onDoneButtonClicked,
- onBack = vm::onBack
+ onBack = vm::onBack,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt
index b9a16c402e59..52263ce64a85 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt
@@ -18,6 +18,7 @@ package com.android.systemui.keyboard.shortcut.ui.view
import android.content.ActivityNotFoundException
import android.content.Intent
+import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
import android.content.res.Configuration
import android.os.Bundle
import android.provider.Settings
@@ -125,7 +126,7 @@ constructor(private val userTracker: UserTracker, private val viewModel: Shortcu
private fun onKeyboardSettingsClicked() {
try {
startActivityAsUser(
- Intent(Settings.ACTION_HARD_KEYBOARD_SETTINGS),
+ Intent(Settings.ACTION_HARD_KEYBOARD_SETTINGS).addFlags(FLAG_ACTIVITY_NEW_TASK),
userTracker.userHandle,
)
} catch (e: ActivityNotFoundException) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
index 68a252b2caba..654c76359505 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
@@ -515,7 +515,13 @@ public class KeyguardSliceProvider extends SliceProvider implements
}
protected void notifyChange() {
- mBgHandler.post(() -> mContentResolver.notifyChange(mSliceUri, null /* observer */));
+ mBgHandler.post(() -> {
+ try {
+ mContentResolver.notifyChange(mSliceUri, null /* observer */);
+ } catch (Exception e) {
+ Log.e(TAG, "Error on mContentResolver.notifyChange()", e);
+ }
+ });
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 0a38ce07a798..9c7cc81c34aa 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -147,6 +147,7 @@ import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.SystemPropertiesHelper;
import com.android.systemui.keyguard.dagger.KeyguardModule;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionBootInteractor;
import com.android.systemui.keyguard.shared.model.TransitionStep;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.navigationbar.NavigationModeController;
@@ -265,6 +266,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
private static final int NOTIFY_STARTED_GOING_TO_SLEEP = 17;
private static final int SYSTEM_READY = 18;
private static final int CANCEL_KEYGUARD_EXIT_ANIM = 19;
+ private static final int BOOT_INTERACTOR = 20;
/** Enum for reasons behind updating wakeAndUnlock state. */
@Retention(RetentionPolicy.SOURCE)
@@ -1390,6 +1392,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
private final DozeParameters mDozeParameters;
private final SelectedUserInteractor mSelectedUserInteractor;
private final KeyguardInteractor mKeyguardInteractor;
+ private final KeyguardTransitionBootInteractor mTransitionBootInteractor;
@VisibleForTesting
protected FoldGracePeriodProvider mFoldGracePeriodProvider =
new FoldGracePeriodProvider();
@@ -1484,6 +1487,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager,
SelectedUserInteractor selectedUserInteractor,
KeyguardInteractor keyguardInteractor,
+ KeyguardTransitionBootInteractor transitionBootInteractor,
WindowManagerOcclusionManager wmOcclusionManager) {
mContext = context;
mUserTracker = userTracker;
@@ -1524,6 +1528,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
mDozeParameters = dozeParameters;
mSelectedUserInteractor = selectedUserInteractor;
mKeyguardInteractor = keyguardInteractor;
+ mTransitionBootInteractor = transitionBootInteractor;
mStatusBarStateController = statusBarStateController;
statusBarStateController.addCallback(this);
@@ -1678,6 +1683,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
adjustStatusBarLocked();
mDreamOverlayStateController.addCallback(mDreamOverlayStateCallback);
+ mHandler.obtainMessage(BOOT_INTERACTOR).sendToTarget();
+
final DreamViewModel dreamViewModel = mDreamViewModel.get();
final CommunalTransitionViewModel communalViewModel =
mCommunalTransitionViewModel.get();
@@ -2705,11 +2712,19 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
message = "SYSTEM_READY";
handleSystemReady();
break;
+ case BOOT_INTERACTOR:
+ message = "BOOT_INTERACTOR";
+ handleBootInteractor();
+ break;
}
Log.d(TAG, "KeyguardViewMediator queue processing message: " + message);
}
};
+ private void handleBootInteractor() {
+ mTransitionBootInteractor.start();
+ }
+
private void tryKeyguardDone() {
if (DEBUG) {
Log.d(TAG, "tryKeyguardDone: pending - " + mKeyguardDonePending + ", animRan - "
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 8a3d01707540..d0a40ec3a361 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -59,6 +59,7 @@ import com.android.systemui.keyguard.data.quickaffordance.KeyguardDataQuickAffor
import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthModule;
import com.android.systemui.keyguard.data.repository.KeyguardRepositoryModule;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionBootInteractor;
import com.android.systemui.keyguard.domain.interactor.StartKeyguardTransitionModule;
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger;
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLoggerImpl;
@@ -175,6 +176,7 @@ public interface KeyguardModule {
Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager,
SelectedUserInteractor selectedUserInteractor,
KeyguardInteractor keyguardInteractor,
+ KeyguardTransitionBootInteractor transitionBootInteractor,
WindowManagerOcclusionManager windowManagerOcclusionManager) {
return new KeyguardViewMediator(
context,
@@ -225,6 +227,7 @@ public interface KeyguardModule {
wmLockscreenVisibilityManager,
selectedUserInteractor,
keyguardInteractor,
+ transitionBootInteractor,
windowManagerOcclusionManager);
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
index 797a4ec419a9..690ae71aa56e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
@@ -23,6 +23,7 @@ import android.annotation.FloatRange
import android.annotation.SuppressLint
import android.os.Trace
import android.util.Log
+import com.android.app.animation.Interpolators
import com.android.app.tracing.coroutines.withContext
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
@@ -95,7 +96,7 @@ interface KeyguardTransitionRepository {
* Emits STARTED and FINISHED transition steps to the given state. This is used during boot to
* seed the repository with the appropriate initial state.
*/
- suspend fun emitInitialStepsFromOff(to: KeyguardState)
+ suspend fun emitInitialStepsFromOff(to: KeyguardState, testSetup: Boolean = false)
/**
* Allows manual control of a transition. When calling [startTransition], the consumer must pass
@@ -108,16 +109,14 @@ interface KeyguardTransitionRepository {
suspend fun updateTransition(
transitionId: UUID,
@FloatRange(from = 0.0, to = 1.0) value: Float,
- state: TransitionState
+ state: TransitionState,
)
}
@SysUISingleton
class KeyguardTransitionRepositoryImpl
@Inject
-constructor(
- @Main val mainDispatcher: CoroutineDispatcher,
-) : KeyguardTransitionRepository {
+constructor(@Main val mainDispatcher: CoroutineDispatcher) : KeyguardTransitionRepository {
/**
* Each transition between [KeyguardState]s will have an associated Flow. In order to collect
* these events, clients should call [transition].
@@ -140,7 +139,7 @@ constructor(
ownerName = "",
from = KeyguardState.OFF,
to = KeyguardState.OFF,
- animator = null
+ animator = null,
)
)
override var currentTransitionInfoInternal = _currentTransitionInfo.asStateFlow()
@@ -159,12 +158,7 @@ constructor(
// to either GONE or LOCKSCREEN once we're booted up and can determine which state we should
// start in.
emitTransition(
- TransitionStep(
- KeyguardState.OFF,
- KeyguardState.OFF,
- 1f,
- TransitionState.FINISHED,
- )
+ TransitionStep(KeyguardState.OFF, KeyguardState.OFF, 1f, TransitionState.FINISHED)
)
}
@@ -217,7 +211,7 @@ constructor(
TransitionStep(
info,
(animation.animatedValue as Float),
- TransitionState.RUNNING
+ TransitionState.RUNNING,
)
)
}
@@ -266,7 +260,7 @@ constructor(
override suspend fun updateTransition(
transitionId: UUID,
@FloatRange(from = 0.0, to = 1.0) value: Float,
- state: TransitionState
+ state: TransitionState,
) {
// There is no fairness guarantee with 'withContext', which means that transitions could
// be processed out of order. Use a Mutex to guarantee ordering. [startTransition]
@@ -282,7 +276,7 @@ constructor(
private suspend fun updateTransitionInternal(
transitionId: UUID,
@FloatRange(from = 0.0, to = 1.0) value: Float,
- state: TransitionState
+ state: TransitionState,
) {
if (updateTransitionId != transitionId) {
Log.e(TAG, "Attempting to update with old/invalid transitionId: $transitionId")
@@ -303,34 +297,51 @@ constructor(
lastStep = nextStep
}
- override suspend fun emitInitialStepsFromOff(to: KeyguardState) {
- _currentTransitionInfo.value =
- TransitionInfo(
- ownerName = "KeyguardTransitionRepository(boot)",
- from = KeyguardState.OFF,
- to = to,
- animator = null
+ override suspend fun emitInitialStepsFromOff(to: KeyguardState, testSetup: Boolean) {
+ val ownerName = "KeyguardTransitionRepository(boot)"
+ // Tests runs on testDispatcher, which is not the main thread, causing the animator thread
+ // check to fail
+ if (testSetup) {
+ _currentTransitionInfo.value =
+ TransitionInfo(
+ ownerName = ownerName,
+ from = KeyguardState.OFF,
+ to = to,
+ animator = null,
+ )
+ emitTransition(
+ TransitionStep(
+ KeyguardState.OFF,
+ to,
+ 0f,
+ TransitionState.STARTED,
+ ownerName = ownerName,
+ )
)
- emitTransition(
- TransitionStep(
- KeyguardState.OFF,
- to,
- 0f,
- TransitionState.STARTED,
- ownerName = "KeyguardTransitionRepository(boot)",
+ emitTransition(
+ TransitionStep(
+ KeyguardState.OFF,
+ to,
+ 1f,
+ TransitionState.FINISHED,
+ ownerName = ownerName,
+ )
)
- )
-
- emitTransition(
- TransitionStep(
- KeyguardState.OFF,
- to,
- 1f,
- TransitionState.FINISHED,
- ownerName = "KeyguardTransitionRepository(boot)",
- ),
- )
+ } else {
+ startTransition(
+ TransitionInfo(
+ ownerName = ownerName,
+ from = KeyguardState.OFF,
+ to = to,
+ animator =
+ ValueAnimator().apply {
+ interpolator = Interpolators.LINEAR
+ duration = 933L
+ },
+ )
+ )
+ }
}
private fun logAndTrace(step: TransitionStep, isManual: Boolean) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
index 0343786bb1fb..840bc0fb5f99 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
@@ -106,7 +106,7 @@ constructor(
startTransitionToLockscreenOrHub(
isIdleOnCommunal,
showCommunalFromOccluded,
- dreamFromOccluded
+ dreamFromOccluded,
)
}
}
@@ -127,7 +127,7 @@ constructor(
startTransitionToLockscreenOrHub(
isIdleOnCommunal,
showCommunalFromOccluded,
- dreamFromOccluded
+ dreamFromOccluded,
)
}
}
@@ -147,7 +147,7 @@ constructor(
communalSceneInteractor.changeScene(
newScene = CommunalScenes.Communal,
loggingReason = "occluded to hub",
- transitionKey = CommunalTransitionKeys.SimpleFade
+ transitionKey = CommunalTransitionKeys.SimpleFade,
)
} else {
startTransitionTo(KeyguardState.GLANCEABLE_HUB)
@@ -210,8 +210,9 @@ constructor(
duration =
when (toState) {
- KeyguardState.LOCKSCREEN -> TO_LOCKSCREEN_DURATION
+ KeyguardState.ALTERNATE_BOUNCER -> TO_ALTERNATE_BOUNCER_DURATION
KeyguardState.GLANCEABLE_HUB -> TO_GLANCEABLE_HUB_DURATION
+ KeyguardState.LOCKSCREEN -> TO_LOCKSCREEN_DURATION
else -> DEFAULT_DURATION
}.inWholeMilliseconds
}
@@ -220,9 +221,10 @@ constructor(
companion object {
const val TAG = "FromOccludedTransitionInteractor"
private val DEFAULT_DURATION = 500.milliseconds
- val TO_LOCKSCREEN_DURATION = 933.milliseconds
- val TO_GLANCEABLE_HUB_DURATION = 250.milliseconds
+ val TO_ALTERNATE_BOUNCER_DURATION = DEFAULT_DURATION
val TO_AOD_DURATION = DEFAULT_DURATION
val TO_DOZING_DURATION = DEFAULT_DURATION
+ val TO_GLANCEABLE_HUB_DURATION = 250.milliseconds
+ val TO_LOCKSCREEN_DURATION = 933.milliseconds
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt
index ea80911335fa..258232b30670 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt
@@ -60,12 +60,12 @@ constructor(
transitionInteractor: KeyguardTransitionInteractor,
val dismissInteractor: KeyguardDismissInteractor,
@Application private val applicationScope: CoroutineScope,
- sceneInteractor: Lazy<SceneInteractor>,
deviceUnlockedInteractor: Lazy<DeviceUnlockedInteractor>,
powerInteractor: PowerInteractor,
alternateBouncerInteractor: AlternateBouncerInteractor,
shadeInteractor: Lazy<ShadeInteractor>,
keyguardInteractor: Lazy<KeyguardInteractor>,
+ sceneInteractor: Lazy<SceneInteractor>,
) {
val dismissAction: Flow<DismissAction> = repository.dismissAction
@@ -102,20 +102,20 @@ constructor(
private val isOnShadeWhileUnlocked: Flow<Boolean> =
if (SceneContainerFlag.isEnabled) {
combine(
- sceneInteractor.get().currentScene,
+ shadeInteractor.get().isAnyExpanded,
deviceUnlockedInteractor.get().deviceUnlockStatus,
- ) { scene, unlockStatus ->
- unlockStatus.isUnlocked &&
- (scene == Scenes.QuickSettings || scene == Scenes.Shade)
+ ) { isAnyExpanded, unlockStatus ->
+ isAnyExpanded && unlockStatus.isUnlocked
}
.distinctUntilChanged()
} else if (ComposeBouncerFlags.isOnlyComposeBouncerEnabled()) {
combine(
- shadeInteractor.get().isAnyExpanded,
- keyguardInteractor.get().isKeyguardDismissible,
- ) { isAnyExpanded, keyguardDismissible ->
- isAnyExpanded && keyguardDismissible
- }
+ shadeInteractor.get().isAnyExpanded,
+ keyguardInteractor.get().isKeyguardDismissible,
+ ) { isAnyExpanded, keyguardDismissible ->
+ isAnyExpanded && keyguardDismissible
+ }
+ .distinctUntilChanged()
} else {
flow {
error(
@@ -127,7 +127,20 @@ constructor(
val executeDismissAction: Flow<() -> KeyguardDone> =
merge(
- finishedTransitionToGone,
+ if (SceneContainerFlag.isEnabled) {
+ // Using currentScene instead of finishedTransitionToGone because of a race
+ // condition that forms between finishedTransitionToGone and
+ // isOnShadeWhileUnlocked where the latter emits false before the former emits
+ // true, causing the merge to not emit until it's too late.
+ sceneInteractor
+ .get()
+ .currentScene
+ .map { it == Scenes.Gone }
+ .distinctUntilChanged()
+ .filter { it }
+ } else {
+ finishedTransitionToGone
+ },
isOnShadeWhileUnlocked.filter { it }.map {},
dismissInteractor.dismissKeyguardRequestWithImmediateDismissAction,
)
@@ -137,10 +150,24 @@ constructor(
val resetDismissAction: Flow<Unit> =
combine(
- transitionInteractor.isFinishedIn(
- scene = Scenes.Gone,
- stateWithoutSceneContainer = GONE,
- ),
+ if (SceneContainerFlag.isEnabled) {
+ // Using currentScene instead of isFinishedIn because of a race condition that
+ // forms between isFinishedIn(Gone) and isOnShadeWhileUnlocked where the latter
+ // emits false before the former emits true, causing the evaluation of the
+ // combine to come up with true, temporarily, before settling on false, which is
+ // a valid final state. That causes an incorrect reset of the dismiss action to
+ // occur before it gets executed.
+ sceneInteractor
+ .get()
+ .currentScene
+ .map { it == Scenes.Gone }
+ .distinctUntilChanged()
+ } else {
+ transitionInteractor.isFinishedIn(
+ scene = Scenes.Gone,
+ stateWithoutSceneContainer = GONE,
+ )
+ },
transitionInteractor.isFinishedIn(
scene = Scenes.Bouncer,
stateWithoutSceneContainer = PRIMARY_BOUNCER,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionBootInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionBootInteractor.kt
index b2183007c48c..89f636d4a270 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionBootInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionBootInteractor.kt
@@ -17,7 +17,6 @@
package com.android.systemui.keyguard.domain.interactor
import android.util.Log
-import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
@@ -46,7 +45,7 @@ constructor(
val keyguardTransitionInteractor: KeyguardTransitionInteractor,
val internalTransitionInteractor: InternalKeyguardTransitionInteractor,
val repository: KeyguardTransitionRepository,
-) : CoreStartable {
+) {
/**
* Whether the lockscreen should be showing when the device starts up for the first time. If not
@@ -60,14 +59,14 @@ constructor(
}
}
- override fun start() {
+ fun start() {
scope.launch {
if (internalTransitionInteractor.currentTransitionInfoInternal.value.from != OFF) {
Log.e(
"KeyguardTransitionInteractor",
"showLockscreenOnBoot emitted, but we've already " +
"transitioned to a state other than OFF. We'll respect that " +
- "transition, but this should not happen."
+ "transition, but this should not happen.",
)
} else {
if (SceneContainerFlag.isEnabled) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
index 25b8fd32e82a..b71533389e2d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
@@ -27,7 +27,6 @@ class KeyguardTransitionCoreStartable
constructor(
private val interactors: Set<TransitionInteractor>,
private val auditLogger: KeyguardTransitionAuditLogger,
- private val bootInteractor: KeyguardTransitionBootInteractor,
private val statusBarDisableFlagsInteractor: StatusBarDisableFlagsInteractor,
private val keyguardStateCallbackInteractor: KeyguardStateCallbackInteractor,
) : CoreStartable {
@@ -54,7 +53,6 @@ constructor(
it.start()
}
auditLogger.start()
- bootInteractor.start()
statusBarDisableFlagsInteractor.start()
keyguardStateCallbackInteractor.start()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
index f1b9cba11051..00aa44fe795b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
@@ -47,56 +47,53 @@ object KeyguardBlueprintViewBinder {
constraintLayout.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
launch("$TAG#viewModel.blueprint") {
- viewModel.blueprint
- .pairwise(
- null as KeyguardBlueprint?,
- )
- .collect { (prevBlueprint, blueprint) ->
- val config = Config.DEFAULT
- val transition =
- if (
- !KeyguardBottomAreaRefactor.isEnabled &&
- prevBlueprint != null &&
- prevBlueprint != blueprint
- ) {
- BaseBlueprintTransition(clockViewModel)
- .addTransition(
- IntraBlueprintTransition(
- config,
- clockViewModel,
- smartspaceViewModel
- )
+ viewModel.blueprint.pairwise(null as KeyguardBlueprint?).collect {
+ (prevBlueprint, blueprint) ->
+ val config = Config.DEFAULT
+ val transition =
+ if (
+ !KeyguardBottomAreaRefactor.isEnabled &&
+ prevBlueprint != null &&
+ prevBlueprint != blueprint
+ ) {
+ BaseBlueprintTransition(clockViewModel)
+ .addTransition(
+ IntraBlueprintTransition(
+ config,
+ clockViewModel,
+ smartspaceViewModel,
)
- } else {
- IntraBlueprintTransition(
- config,
- clockViewModel,
- smartspaceViewModel
)
- }
-
- viewModel.runTransition(constraintLayout, transition, config) {
- // Replace sections from the previous blueprint with the new ones
- blueprint.replaceViews(
- constraintLayout,
- prevBlueprint,
- config.rebuildSections
+ } else {
+ IntraBlueprintTransition(
+ config,
+ clockViewModel,
+ smartspaceViewModel,
)
+ }
+
+ viewModel.runTransition(constraintLayout, transition, config) {
+ // Replace sections from the previous blueprint with the new ones
+ blueprint.replaceViews(
+ constraintLayout,
+ prevBlueprint,
+ config.rebuildSections,
+ )
- val cs =
- ConstraintSet().apply {
- clone(constraintLayout)
- val emptyLayout = ConstraintSet.Layout()
- knownIds.forEach {
- getConstraint(it).layout.copyFrom(emptyLayout)
- }
- blueprint.applyConstraints(this)
+ val cs =
+ ConstraintSet().apply {
+ clone(constraintLayout)
+ val emptyLayout = ConstraintSet.Layout()
+ knownIds.forEach {
+ getConstraint(it).layout.copyFrom(emptyLayout)
}
+ blueprint.applyConstraints(this)
+ }
- logAlphaVisibilityScaleOfAppliedConstraintSet(cs, clockViewModel)
- cs.applyTo(constraintLayout)
- }
+ logAlphaVisibilityScaleOfAppliedConstraintSet(cs, clockViewModel)
+ cs.applyTo(constraintLayout)
}
+ }
}
launch("$TAG#viewModel.refreshTransition") {
@@ -105,7 +102,8 @@ object KeyguardBlueprintViewBinder {
viewModel.runTransition(
constraintLayout,
- IntraBlueprintTransition(config, clockViewModel, smartspaceViewModel),
+ clockViewModel,
+ smartspaceViewModel,
config,
) {
blueprint.rebuildViews(constraintLayout, config.rebuildSections)
@@ -126,7 +124,7 @@ object KeyguardBlueprintViewBinder {
private fun logAlphaVisibilityScaleOfAppliedConstraintSet(
cs: ConstraintSet,
- viewModel: KeyguardClockViewModel
+ viewModel: KeyguardClockViewModel,
) {
val currentClock = viewModel.currentClock.value
if (!DEBUG || currentClock == null) return
@@ -137,19 +135,19 @@ object KeyguardBlueprintViewBinder {
TAG,
"applyCsToSmallClock: vis=${cs.getVisibility(smallClockViewId)} " +
"alpha=${cs.getConstraint(smallClockViewId).propertySet.alpha} " +
- "scale=${cs.getConstraint(smallClockViewId).transform.scaleX} "
+ "scale=${cs.getConstraint(smallClockViewId).transform.scaleX} ",
)
Log.i(
TAG,
"applyCsToLargeClock: vis=${cs.getVisibility(largeClockViewId)} " +
"alpha=${cs.getConstraint(largeClockViewId).propertySet.alpha} " +
"scale=${cs.getConstraint(largeClockViewId).transform.scaleX} " +
- "pivotX=${cs.getConstraint(largeClockViewId).transform.transformPivotX} "
+ "pivotX=${cs.getConstraint(largeClockViewId).transform.transformPivotX} ",
)
Log.i(
TAG,
"applyCsToSmartspaceDate: vis=${cs.getVisibility(smartspaceDateId)} " +
- "alpha=${cs.getConstraint(smartspaceDateId).propertySet.alpha}"
+ "alpha=${cs.getConstraint(smartspaceDateId).propertySet.alpha}",
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/IntraBlueprintTransition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/IntraBlueprintTransition.kt
index aa0a9d9cee1f..9a55f7bab33b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/IntraBlueprintTransition.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/IntraBlueprintTransition.kt
@@ -29,18 +29,18 @@ class IntraBlueprintTransition(
smartspaceViewModel: KeyguardSmartspaceViewModel,
) : TransitionSet() {
- enum class Type(
- val priority: Int,
- val animateNotifChanges: Boolean,
- ) {
+ enum class Type(val priority: Int, val animateNotifChanges: Boolean) {
ClockSize(100, true),
ClockCenter(99, false),
DefaultClockStepping(98, false),
- SmartspaceVisibility(2, true),
- DefaultTransition(1, false),
+ SmartspaceVisibility(3, true),
+ DefaultTransition(2, false),
// When transition between blueprint, we don't need any duration or interpolator but we need
// all elements go to correct state
- NoTransition(0, false),
+ NoTransition(1, false),
+ // Similar to NoTransition, except also does not explicitly update any alpha. Used in
+ // OFF->LOCKSCREEN transition
+ Init(0, false),
}
data class Config(
@@ -57,6 +57,7 @@ class IntraBlueprintTransition(
init {
ordering = ORDERING_TOGETHER
when (config.type) {
+ Type.Init -> {}
Type.NoTransition -> {}
Type.DefaultClockStepping ->
addTransition(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
index ff848264db68..a1c963b3137a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
@@ -53,14 +53,11 @@ import kotlinx.coroutines.DisposableHandle
internal fun ConstraintSet.setVisibility(views: Iterable<View>, visibility: Int) =
views.forEach { view -> this.setVisibility(view.id, visibility) }
-internal fun ConstraintSet.setAlpha(views: Iterable<View>, alpha: Float) =
- views.forEach { view -> this.setAlpha(view.id, alpha) }
+internal fun ConstraintSet.setScaleX(views: Iterable<View>, scaleX: Float) =
+ views.forEach { view -> this.setScaleX(view.id, scaleX) }
-internal fun ConstraintSet.setScaleX(views: Iterable<View>, alpha: Float) =
- views.forEach { view -> this.setScaleX(view.id, alpha) }
-
-internal fun ConstraintSet.setScaleY(views: Iterable<View>, alpha: Float) =
- views.forEach { view -> this.setScaleY(view.id, alpha) }
+internal fun ConstraintSet.setScaleY(views: Iterable<View>, scaleY: Float) =
+ views.forEach { view -> this.setScaleY(view.id, scaleY) }
@SysUISingleton
class ClockSection
@@ -126,8 +123,6 @@ constructor(
return constraintSet.apply {
setVisibility(getTargetClockFace(clock).views, VISIBLE)
setVisibility(getNonTargetClockFace(clock).views, GONE)
- setAlpha(getTargetClockFace(clock).views, 1F)
- setAlpha(getNonTargetClockFace(clock).views, 0F)
if (!keyguardClockViewModel.isLargeClockVisible.value) {
connect(sharedR.id.bc_smartspace_view, TOP, sharedR.id.date_smartspace_view, BOTTOM)
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToOccludedTransitionViewModel.kt
index 3f2ef29c9570..c49e7833e349 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToOccludedTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToOccludedTransitionViewModel.kt
@@ -28,22 +28,22 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
/**
- * Breaks down ALTERNATE_BOUNCER->GONE transition into discrete steps for corresponding views to
+ * Breaks down ALTERNATE_BOUNCER->OCCLUDED transition into discrete steps for corresponding views to
* consume.
*/
@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class AlternateBouncerToOccludedTransitionViewModel
@Inject
-constructor(
- animationFlow: KeyguardTransitionAnimationFlow,
-) : DeviceEntryIconTransition {
+constructor(animationFlow: KeyguardTransitionAnimationFlow) : DeviceEntryIconTransition {
private val transitionAnimation =
animationFlow.setup(
duration = TO_OCCLUDED_DURATION,
edge = Edge.create(from = ALTERNATE_BOUNCER, to = OCCLUDED),
)
+ val lockscreenAlpha: Flow<Float> = transitionAnimation.immediatelyTransitionTo(0f)
+
override val deviceEntryParentViewAlpha: Flow<Float> =
transitionAnimation.immediatelyTransitionTo(0f)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
index a021de446911..ca1a8006c6bb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryBackgroundViewModel.kt
@@ -56,6 +56,7 @@ constructor(
occludedToAodTransitionViewModel: OccludedToAodTransitionViewModel,
occludedToDozingTransitionViewModel: OccludedToDozingTransitionViewModel,
occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel,
+ offToLockscreenTransitionViewModel: OffToLockscreenTransitionViewModel,
primaryBouncerToAodTransitionViewModel: PrimaryBouncerToAodTransitionViewModel,
primaryBouncerToDozingTransitionViewModel: PrimaryBouncerToDozingTransitionViewModel,
primaryBouncerToLockscreenTransitionViewModel: PrimaryBouncerToLockscreenTransitionViewModel,
@@ -67,14 +68,14 @@ constructor(
.map {
Utils.getColorAttrDefaultColor(
context,
- com.android.internal.R.attr.colorSurface
+ com.android.internal.R.attr.colorSurface,
)
}
.onStart {
emit(
Utils.getColorAttrDefaultColor(
context,
- com.android.internal.R.attr.colorSurface
+ com.android.internal.R.attr.colorSurface,
)
)
}
@@ -86,23 +87,23 @@ constructor(
deviceEntryIconViewModel.useBackgroundProtection.flatMapLatest { useBackground ->
if (useBackground) {
setOf(
- lockscreenToAodTransitionViewModel.deviceEntryBackgroundViewAlpha,
+ alternateBouncerToAodTransitionViewModel.deviceEntryBackgroundViewAlpha,
+ alternateBouncerToDozingTransitionViewModel.deviceEntryBackgroundViewAlpha,
aodToLockscreenTransitionViewModel.deviceEntryBackgroundViewAlpha,
+ dozingToLockscreenTransitionViewModel.deviceEntryBackgroundViewAlpha,
+ dreamingToAodTransitionViewModel.deviceEntryBackgroundViewAlpha,
+ dreamingToLockscreenTransitionViewModel.deviceEntryBackgroundViewAlpha,
goneToAodTransitionViewModel.deviceEntryBackgroundViewAlpha,
- primaryBouncerToAodTransitionViewModel.deviceEntryBackgroundViewAlpha,
+ goneToDozingTransitionViewModel.deviceEntryBackgroundViewAlpha,
+ goneToLockscreenTransitionViewModel.deviceEntryBackgroundViewAlpha,
+ lockscreenToAodTransitionViewModel.deviceEntryBackgroundViewAlpha,
occludedToAodTransitionViewModel.deviceEntryBackgroundViewAlpha,
+ occludedToDozingTransitionViewModel.deviceEntryBackgroundViewAlpha,
occludedToLockscreenTransitionViewModel.deviceEntryBackgroundViewAlpha,
- dreamingToLockscreenTransitionViewModel.deviceEntryBackgroundViewAlpha,
- alternateBouncerToAodTransitionViewModel.deviceEntryBackgroundViewAlpha,
- goneToLockscreenTransitionViewModel.deviceEntryBackgroundViewAlpha,
- goneToDozingTransitionViewModel.deviceEntryBackgroundViewAlpha,
+ offToLockscreenTransitionViewModel.deviceEntryBackgroundViewAlpha,
+ primaryBouncerToAodTransitionViewModel.deviceEntryBackgroundViewAlpha,
primaryBouncerToDozingTransitionViewModel.deviceEntryBackgroundViewAlpha,
- dozingToLockscreenTransitionViewModel.deviceEntryBackgroundViewAlpha,
- alternateBouncerToDozingTransitionViewModel.deviceEntryBackgroundViewAlpha,
- dreamingToAodTransitionViewModel.deviceEntryBackgroundViewAlpha,
- primaryBouncerToLockscreenTransitionViewModel
- .deviceEntryBackgroundViewAlpha,
- occludedToDozingTransitionViewModel.deviceEntryBackgroundViewAlpha,
+ primaryBouncerToLockscreenTransitionViewModel.deviceEntryBackgroundViewAlpha,
)
.merge()
.onStart {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt
index 4cf3c4e7f6d0..1289036c7ae0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt
@@ -24,6 +24,9 @@ import android.util.Log
import androidx.constraintlayout.widget.ConstraintLayout
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition
import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Config
import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Type
import javax.inject.Inject
@@ -37,6 +40,7 @@ class KeyguardBlueprintViewModel
constructor(
@Main private val handler: Handler,
private val keyguardBlueprintInteractor: KeyguardBlueprintInteractor,
+ private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
) {
val blueprint = keyguardBlueprintInteractor.blueprint
val blueprintId = keyguardBlueprintInteractor.blueprintId
@@ -49,12 +53,12 @@ constructor(
private val transitionListener =
object : Transition.TransitionListener {
override fun onTransitionCancel(transition: Transition) {
- if (DEBUG) Log.e(TAG, "onTransitionCancel: ${transition::class.simpleName}")
+ if (DEBUG) Log.w(TAG, "onTransitionCancel: ${transition::class.simpleName}")
updateTransitions(null) { remove(transition) }
}
override fun onTransitionEnd(transition: Transition) {
- if (DEBUG) Log.e(TAG, "onTransitionEnd: ${transition::class.simpleName}")
+ if (DEBUG) Log.i(TAG, "onTransitionEnd: ${transition::class.simpleName}")
updateTransitions(null) { remove(transition) }
}
@@ -86,6 +90,28 @@ constructor(
fun runTransition(
constraintLayout: ConstraintLayout,
+ clockViewModel: KeyguardClockViewModel,
+ smartspaceViewModel: KeyguardSmartspaceViewModel,
+ config: Config,
+ apply: () -> Unit,
+ ) {
+ val newConfig =
+ if (keyguardTransitionInteractor.getCurrentState() == KeyguardState.OFF) {
+ config.copy(type = Type.Init)
+ } else {
+ config
+ }
+
+ runTransition(
+ constraintLayout,
+ IntraBlueprintTransition(newConfig, clockViewModel, smartspaceViewModel),
+ config,
+ apply,
+ )
+ }
+
+ fun runTransition(
+ constraintLayout: ConstraintLayout,
transition: Transition,
config: Config,
apply: () -> Unit,
@@ -103,21 +129,29 @@ constructor(
return
}
+ // Don't allow transitions with animations while in OFF state
+ val newConfig =
+ if (keyguardTransitionInteractor.getCurrentState() == KeyguardState.OFF) {
+ config.copy(type = Type.Init)
+ } else {
+ config
+ }
+
if (DEBUG) {
Log.i(
TAG,
"runTransition: running ${transition::class.simpleName}: " +
- "currentPriority=$currentPriority; config=$config",
+ "currentPriority=$currentPriority; config=$newConfig",
)
}
// beginDelayedTransition makes a copy, so we temporarially add the uncopied transition to
// the running set until the copy is started by the handler.
- updateTransitions(TransitionData(config)) { add(transition) }
+ updateTransitions(TransitionData(newConfig)) { add(transition) }
transition.addListener(transitionListener)
handler.post {
- if (config.terminatePrevious) {
+ if (newConfig.terminatePrevious) {
TransitionManager.endTransitions(constraintLayout)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index 10a2e5c04b00..3705c2c19110 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -20,7 +20,6 @@ package com.android.systemui.keyguard.ui.viewmodel
import android.graphics.Point
import android.util.MathUtils
import android.view.View.VISIBLE
-import com.android.app.tracing.coroutines.launch
import com.android.systemui.common.shared.model.NotificationContainerBounds
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.dagger.SysUISingleton
@@ -35,6 +34,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
+import com.android.systemui.keyguard.shared.model.KeyguardState.OFF
import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
@@ -88,6 +88,8 @@ constructor(
AlternateBouncerToGoneTransitionViewModel,
private val alternateBouncerToLockscreenTransitionViewModel:
AlternateBouncerToLockscreenTransitionViewModel,
+ private val alternateBouncerToOccludedTransitionViewModel:
+ AlternateBouncerToOccludedTransitionViewModel,
private val aodToGoneTransitionViewModel: AodToGoneTransitionViewModel,
private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
private val aodToOccludedTransitionViewModel: AodToOccludedTransitionViewModel,
@@ -112,9 +114,12 @@ constructor(
private val lockscreenToOccludedTransitionViewModel: LockscreenToOccludedTransitionViewModel,
private val lockscreenToPrimaryBouncerTransitionViewModel:
LockscreenToPrimaryBouncerTransitionViewModel,
+ private val occludedToAlternateBouncerTransitionViewModel:
+ OccludedToAlternateBouncerTransitionViewModel,
private val occludedToAodTransitionViewModel: OccludedToAodTransitionViewModel,
private val occludedToDozingTransitionViewModel: OccludedToDozingTransitionViewModel,
private val occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel,
+ private val offToLockscreenTransitionViewModel: OffToLockscreenTransitionViewModel,
private val primaryBouncerToAodTransitionViewModel: PrimaryBouncerToAodTransitionViewModel,
private val primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel,
private val primaryBouncerToLockscreenTransitionViewModel:
@@ -201,6 +206,10 @@ constructor(
notificationShadeWindowModel.isKeyguardOccluded,
communalInteractor.isIdleOnCommunal,
keyguardTransitionInteractor
+ .transitionValue(OFF)
+ .map { it > 1f - offToLockscreenTransitionViewModel.alphaStartAt }
+ .onStart { emit(false) },
+ keyguardTransitionInteractor
.transitionValue(scene = Scenes.Gone, stateWithoutSceneContainer = GONE)
.map { it == 1f }
.onStart { emit(false) },
@@ -227,6 +236,7 @@ constructor(
alternateBouncerToAodTransitionViewModel.lockscreenAlpha(viewState),
alternateBouncerToGoneTransitionViewModel.lockscreenAlpha(viewState),
alternateBouncerToLockscreenTransitionViewModel.lockscreenAlpha(viewState),
+ alternateBouncerToOccludedTransitionViewModel.lockscreenAlpha,
aodToGoneTransitionViewModel.lockscreenAlpha(viewState),
aodToLockscreenTransitionViewModel.lockscreenAlpha(viewState),
aodToOccludedTransitionViewModel.lockscreenAlpha(viewState),
@@ -249,14 +259,16 @@ constructor(
lockscreenToGoneTransitionViewModel.lockscreenAlpha(viewState),
lockscreenToOccludedTransitionViewModel.lockscreenAlpha,
lockscreenToPrimaryBouncerTransitionViewModel.lockscreenAlpha,
+ occludedToAlternateBouncerTransitionViewModel.lockscreenAlpha,
occludedToAodTransitionViewModel.lockscreenAlpha,
occludedToDozingTransitionViewModel.lockscreenAlpha,
occludedToLockscreenTransitionViewModel.lockscreenAlpha,
+ offToLockscreenTransitionViewModel.lockscreenAlpha,
primaryBouncerToAodTransitionViewModel.lockscreenAlpha,
primaryBouncerToGoneTransitionViewModel.lockscreenAlpha,
primaryBouncerToLockscreenTransitionViewModel.lockscreenAlpha(viewState),
)
- .onStart { emit(1f) },
+ .onStart { emit(0f) },
) { hideKeyguard, alpha ->
if (hideKeyguard) {
0f
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt
index 8d9ccef95f0d..88e8968501dd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt
@@ -52,18 +52,26 @@ constructor(
/** Lockscreen views alpha */
val lockscreenAlpha: Flow<Float> =
- transitionAnimation.sharedFlow(
- duration = 250.milliseconds,
- onStep = { 1f - it },
- name = "LOCKSCREEN->OCCLUDED: lockscreenAlpha",
+ shadeDependentFlows.transitionFlow(
+ flowWhenShadeIsNotExpanded =
+ transitionAnimation.sharedFlow(
+ duration = 250.milliseconds,
+ onStep = { 1f - it },
+ name = "LOCKSCREEN->OCCLUDED: lockscreenAlpha",
+ ),
+ flowWhenShadeIsExpanded = transitionAnimation.immediatelyTransitionTo(0f),
)
val shortcutsAlpha: Flow<Float> =
- transitionAnimation.sharedFlow(
- duration = 250.milliseconds,
- onStep = { 1 - it },
- onFinish = { 0f },
- onCancel = { 1f },
+ shadeDependentFlows.transitionFlow(
+ flowWhenShadeIsNotExpanded =
+ transitionAnimation.sharedFlow(
+ duration = 250.milliseconds,
+ onStep = { 1f - it },
+ onFinish = { 0f },
+ onCancel = { 1f },
+ ),
+ flowWhenShadeIsExpanded = transitionAnimation.immediatelyTransitionTo(0f),
)
/** Lockscreen views y-translation */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModel.kt
index 3b266f945aab..6f29004d4f3f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenUserActionsViewModel.kt
@@ -18,20 +18,18 @@
package com.android.systemui.keyguard.ui.viewmodel
-import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.Swipe
-import com.android.compose.animation.scene.SwipeDirection
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.DeviceEntryInteractor
-import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade
-import com.android.systemui.scene.ui.viewmodel.SceneContainerEdge
import com.android.systemui.scene.ui.viewmodel.UserActionsViewModel
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.shared.model.ShadeMode
+import com.android.systemui.shade.ui.viewmodel.dualShadeActions
+import com.android.systemui.shade.ui.viewmodel.singleShadeActions
+import com.android.systemui.shade.ui.viewmodel.splitShadeActions
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -62,7 +60,7 @@ constructor(
) { isDeviceUnlocked, isCommunalAvailable, shadeMode ->
buildList {
if (isCommunalAvailable) {
- add(Swipe.Left to Scenes.Communal)
+ add(Swipe.Start to Scenes.Communal)
}
add(Swipe.Up to if (isDeviceUnlocked) Scenes.Gone else Scenes.Bouncer)
@@ -81,45 +79,6 @@ constructor(
.collect { setActions(it) }
}
- private fun singleShadeActions(): Array<Pair<UserAction, UserActionResult>> {
- return arrayOf(
- // Swiping down, not from the edge, always goes to shade.
- Swipe.Down to Scenes.Shade,
- swipeDown(pointerCount = 2) to Scenes.Shade,
- // Swiping down from the top edge goes to QS.
- swipeDownFromTop(pointerCount = 1) to Scenes.QuickSettings,
- swipeDownFromTop(pointerCount = 2) to Scenes.QuickSettings,
- )
- }
-
- private fun splitShadeActions(): Array<Pair<UserAction, UserActionResult>> {
- val splitShadeSceneKey = UserActionResult(Scenes.Shade, ToSplitShade)
- return arrayOf(
- // Swiping down, not from the edge, always goes to shade.
- Swipe.Down to splitShadeSceneKey,
- swipeDown(pointerCount = 2) to splitShadeSceneKey,
- // Swiping down from the top edge goes to QS.
- swipeDownFromTop(pointerCount = 1) to splitShadeSceneKey,
- swipeDownFromTop(pointerCount = 2) to splitShadeSceneKey,
- )
- }
-
- private fun dualShadeActions(): Array<Pair<UserAction, UserActionResult>> {
- return arrayOf(
- Swipe.Down to Overlays.NotificationsShade,
- Swipe(direction = SwipeDirection.Down, fromSource = SceneContainerEdge.TopRight) to
- Overlays.QuickSettingsShade,
- )
- }
-
- private fun swipeDownFromTop(pointerCount: Int): Swipe {
- return Swipe(SwipeDirection.Down, fromSource = Edge.Top, pointerCount = pointerCount)
- }
-
- private fun swipeDown(pointerCount: Int): Swipe {
- return Swipe(SwipeDirection.Down, pointerCount = pointerCount)
- }
-
@AssistedFactory
interface Factory {
fun create(): LockscreenUserActionsViewModel
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAlternateBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAlternateBouncerTransitionViewModel.kt
new file mode 100644
index 000000000000..5bfcccbaccaa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAlternateBouncerTransitionViewModel.kt
@@ -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 com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.FromOccludedTransitionInteractor.Companion.TO_ALTERNATE_BOUNCER_DURATION
+import com.android.systemui.keyguard.shared.model.Edge
+import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
+import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Breaks down OCCLUDED->ALTERNATE_BOUNCER transition into discrete steps for corresponding views to
+ * consume.
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class OccludedToAlternateBouncerTransitionViewModel
+@Inject
+constructor(animationFlow: KeyguardTransitionAnimationFlow) : DeviceEntryIconTransition {
+ private val transitionAnimation =
+ animationFlow.setup(
+ duration = TO_ALTERNATE_BOUNCER_DURATION,
+ edge = Edge.create(from = OCCLUDED, to = ALTERNATE_BOUNCER),
+ )
+
+ val lockscreenAlpha: Flow<Float> = transitionAnimation.immediatelyTransitionTo(0f)
+
+ override val deviceEntryParentViewAlpha: Flow<Float> =
+ transitionAnimation.immediatelyTransitionTo(0f)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OffToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OffToLockscreenTransitionViewModel.kt
index 1eecbd5fbda1..b4acce66da4f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OffToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OffToLockscreenTransitionViewModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.ui.viewmodel
+import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.shared.model.Edge
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
@@ -29,23 +30,29 @@ import kotlinx.coroutines.flow.Flow
@SysUISingleton
class OffToLockscreenTransitionViewModel
@Inject
-constructor(
- animationFlow: KeyguardTransitionAnimationFlow,
-) : DeviceEntryIconTransition {
+constructor(animationFlow: KeyguardTransitionAnimationFlow) : DeviceEntryIconTransition {
+
+ private val startTime = 300.milliseconds
+ private val alphaDuration = 633.milliseconds
+ val alphaStartAt = startTime / (alphaDuration + startTime)
private val transitionAnimation =
animationFlow.setup(
- duration = 250.milliseconds,
+ duration = startTime + alphaDuration,
edge = Edge.create(from = OFF, to = LOCKSCREEN),
)
- val shortcutsAlpha: Flow<Float> =
+ val lockscreenAlpha: Flow<Float> =
transitionAnimation.sharedFlow(
- duration = 250.milliseconds,
+ startTime = startTime,
+ duration = alphaDuration,
+ interpolator = EMPHASIZED_ACCELERATE,
onStep = { it },
- onCancel = { 0f },
)
- override val deviceEntryParentViewAlpha: Flow<Float> =
- transitionAnimation.immediatelyTransitionTo(1f)
+ val shortcutsAlpha: Flow<Float> = lockscreenAlpha
+
+ override val deviceEntryParentViewAlpha: Flow<Float> = lockscreenAlpha
+
+ val deviceEntryBackgroundViewAlpha: Flow<Float> = lockscreenAlpha
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt
index 84aae652795e..222d783ab79a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt
@@ -111,7 +111,7 @@ private val ART_URIS =
arrayOf(
MediaMetadata.METADATA_KEY_ALBUM_ART_URI,
MediaMetadata.METADATA_KEY_ART_URI,
- MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI
+ MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI,
)
private const val TAG = "MediaDataManager"
@@ -136,7 +136,7 @@ private val LOADING =
active = true,
resumeAction = null,
instanceId = InstanceId.fakeInstanceId(-1),
- appUid = Process.INVALID_UID
+ appUid = Process.INVALID_UID,
)
internal val EMPTY_SMARTSPACE_MEDIA_DATA =
@@ -163,7 +163,7 @@ private fun allowMediaRecommendations(context: Context): Boolean {
Settings.Secure.getInt(
context.contentResolver,
Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
- 1
+ 1,
)
return Utils.useQsMediaPlayer(context) && flag > 0
}
@@ -217,7 +217,7 @@ class LegacyMediaDataManagerImpl(
private val themeText =
com.android.settingslib.Utils.getColorAttr(
context,
- com.android.internal.R.attr.textColorPrimary
+ com.android.internal.R.attr.textColorPrimary,
)
.defaultColor
@@ -387,7 +387,7 @@ class LegacyMediaDataManagerImpl(
uiExecutor,
SmartspaceSession.OnTargetsAvailableListener { targets ->
smartspaceMediaDataProvider.onTargetsAvailable(targets)
- }
+ },
)
}
smartspaceSession?.let { it.requestSmartspaceUpdate() }
@@ -398,12 +398,12 @@ class LegacyMediaDataManagerImpl(
if (!allowMediaRecommendations) {
dismissSmartspaceRecommendation(
key = smartspaceMediaData.targetId,
- delay = 0L
+ delay = 0L,
)
}
}
},
- Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION
+ Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
)
}
@@ -461,7 +461,7 @@ class LegacyMediaDataManagerImpl(
token: MediaSession.Token,
appName: String,
appIntent: PendingIntent,
- packageName: String
+ packageName: String,
) {
// Resume controls don't have a notification key, so store by package name instead
if (!mediaEntries.containsKey(packageName)) {
@@ -497,7 +497,7 @@ class LegacyMediaDataManagerImpl(
token,
appName,
appIntent,
- packageName
+ packageName,
)
}
} else {
@@ -509,7 +509,7 @@ class LegacyMediaDataManagerImpl(
token,
appName,
appIntent,
- packageName
+ packageName,
)
}
}
@@ -609,14 +609,14 @@ class LegacyMediaDataManagerImpl(
result.appUid,
sbn.packageName,
instanceId,
- result.playbackLocation
+ result.playbackLocation,
)
} else if (result.playbackLocation != currentEntry?.playbackLocation) {
logger.logPlaybackLocationChange(
result.appUid,
sbn.packageName,
instanceId,
- result.playbackLocation
+ result.playbackLocation,
)
}
@@ -722,30 +722,32 @@ class LegacyMediaDataManagerImpl(
/** Called when the player's [PlaybackState] has been updated with new actions and/or state */
private fun updateState(key: String, state: PlaybackState) {
mediaEntries.get(key)?.let {
- val token = it.token
- if (token == null) {
- if (DEBUG) Log.d(TAG, "State updated, but token was null")
- return
- }
- val actions =
- createActionsFromState(
- it.packageName,
- mediaControllerFactory.create(it.token),
- UserHandle(it.userId)
- )
-
- // Control buttons
- // If flag is enabled and controller has a PlaybackState,
- // create actions from session info
- // otherwise, no need to update semantic actions.
- val data =
- if (actions != null) {
- it.copy(semanticActions = actions, isPlaying = isPlayingState(state.state))
- } else {
- it.copy(isPlaying = isPlayingState(state.state))
+ backgroundExecutor.execute {
+ val token = it.token
+ if (token == null) {
+ if (DEBUG) Log.d(TAG, "State updated, but token was null")
+ return@execute
}
- if (DEBUG) Log.d(TAG, "State updated outside of notification")
- onMediaDataLoaded(key, key, data)
+ val actions =
+ createActionsFromState(
+ it.packageName,
+ mediaControllerFactory.create(it.token),
+ UserHandle(it.userId),
+ )
+
+ // Control buttons
+ // If flag is enabled and controller has a PlaybackState,
+ // create actions from session info
+ // otherwise, no need to update semantic actions.
+ val data =
+ if (actions != null) {
+ it.copy(semanticActions = actions, isPlaying = isPlayingState(state.state))
+ } else {
+ it.copy(isPlaying = isPlayingState(state.state))
+ }
+ if (DEBUG) Log.d(TAG, "State updated outside of notification")
+ foregroundExecutor.execute { onMediaDataLoaded(key, key, data) }
+ }
}
}
@@ -773,7 +775,7 @@ class LegacyMediaDataManagerImpl(
}
foregroundExecutor.executeDelayed(
{ removeEntry(key = key, userInitiated = userInitiated) },
- delay
+ delay,
)
return existed
}
@@ -793,12 +795,12 @@ class LegacyMediaDataManagerImpl(
smartspaceMediaData =
EMPTY_SMARTSPACE_MEDIA_DATA.copy(
targetId = smartspaceMediaData.targetId,
- instanceId = smartspaceMediaData.instanceId
+ instanceId = smartspaceMediaData.instanceId,
)
}
foregroundExecutor.executeDelayed(
{ notifySmartspaceMediaDataRemoved(smartspaceMediaData.targetId, immediately = true) },
- delay
+ delay,
)
}
@@ -826,7 +828,7 @@ class LegacyMediaDataManagerImpl(
token: MediaSession.Token,
appName: String,
appIntent: PendingIntent,
- packageName: String
+ packageName: String,
) =
withContext(backgroundDispatcher) {
val lastActive = systemClock.elapsedRealtime()
@@ -843,7 +845,7 @@ class LegacyMediaDataManagerImpl(
token,
appName,
appIntent,
- packageName
+ packageName,
)
if (result == null || desc.title.isNullOrBlank()) {
Log.d(TAG, "No MediaData result for resumption")
@@ -882,7 +884,7 @@ class LegacyMediaDataManagerImpl(
appUid = result.appUid,
isExplicit = result.isExplicit,
resumeProgress = result.resumeProgress,
- )
+ ),
)
}
}
@@ -895,7 +897,7 @@ class LegacyMediaDataManagerImpl(
token: MediaSession.Token,
appName: String,
appIntent: PendingIntent,
- packageName: String
+ packageName: String,
) {
if (desc.title.isNullOrBlank()) {
Log.e(TAG, "Description incomplete")
@@ -966,7 +968,7 @@ class LegacyMediaDataManagerImpl(
appUid = appUid,
isExplicit = isExplicit,
resumeProgress = progress,
- )
+ ),
)
}
}
@@ -981,7 +983,7 @@ class LegacyMediaDataManagerImpl(
val token =
sbn.notification.extras.getParcelable(
Notification.EXTRA_MEDIA_SESSION,
- MediaSession.Token::class.java
+ MediaSession.Token::class.java,
)
if (token == null) {
return
@@ -993,7 +995,7 @@ class LegacyMediaDataManagerImpl(
val appInfo =
notif.extras.getParcelable(
Notification.EXTRA_BUILDER_APPLICATION_INFO,
- ApplicationInfo::class.java
+ ApplicationInfo::class.java,
) ?: getAppInfoFromPackage(sbn.packageName)
// App name
@@ -1057,7 +1059,7 @@ class LegacyMediaDataManagerImpl(
val deviceIntent =
extras.getParcelable(
Notification.EXTRA_MEDIA_REMOTE_INTENT,
- PendingIntent::class.java
+ PendingIntent::class.java,
)
Log.d(TAG, "$key is RCN for $deviceName")
@@ -1073,7 +1075,7 @@ class LegacyMediaDataManagerImpl(
deviceDrawable,
deviceName,
deviceIntent,
- showBroadcastButton = false
+ showBroadcastButton = false,
)
}
}
@@ -1160,7 +1162,7 @@ class LegacyMediaDataManagerImpl(
mediaData.copy(
resumeAction = oldResumeAction,
hasCheckedForResume = oldHasCheckedForResume,
- active = oldActive
+ active = oldActive,
)
onMediaDataLoaded(key, oldKey, mediaData)
}
@@ -1169,7 +1171,7 @@ class LegacyMediaDataManagerImpl(
private fun logSingleVsMultipleMediaAdded(
appUid: Int,
packageName: String,
- instanceId: InstanceId
+ instanceId: InstanceId,
) {
if (mediaEntries.size == 1) {
logger.logSingleMediaPlayerInCarousel(appUid, packageName, instanceId)
@@ -1207,7 +1209,7 @@ class LegacyMediaDataManagerImpl(
private fun createActionsFromState(
packageName: String,
controller: MediaController,
- user: UserHandle
+ user: UserHandle,
): MediaButton? {
if (!mediaFlags.areMediaSessionActionsEnabled(packageName, user)) {
return null
@@ -1245,7 +1247,7 @@ class LegacyMediaDataManagerImpl(
packageName,
ContentProvider.getUriWithoutUserId(uri),
Intent.FLAG_GRANT_READ_URI_PERMISSION,
- ContentProvider.getUserIdFromUri(uri, userId)
+ ContentProvider.getUserIdFromUri(uri, userId),
)
return loadBitmapFromUri(uri)
} catch (e: SecurityException) {
@@ -1282,7 +1284,7 @@ class LegacyMediaDataManagerImpl(
val scale =
MediaDataUtils.getScaleFactor(
APair(width, height),
- APair(artworkWidth, artworkHeight)
+ APair(artworkWidth, artworkHeight),
)
// Downscale if needed
@@ -1307,7 +1309,7 @@ class LegacyMediaDataManagerImpl(
.loadDrawable(context),
action,
context.getString(R.string.controls_media_resume),
- context.getDrawable(R.drawable.ic_media_play_container)
+ context.getDrawable(R.drawable.ic_media_play_container),
)
}
@@ -1371,10 +1373,7 @@ class LegacyMediaDataManagerImpl(
// There should NOT be more than 1 Smartspace media update. When it happens, it
// indicates a bad state or an error. Reset the status accordingly.
Log.wtf(TAG, "More than 1 Smartspace Media Update. Resetting the status...")
- notifySmartspaceMediaDataRemoved(
- smartspaceMediaData.targetId,
- immediately = false,
- )
+ notifySmartspaceMediaDataRemoved(smartspaceMediaData.targetId, immediately = false)
smartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA
}
}
@@ -1420,7 +1419,7 @@ class LegacyMediaDataManagerImpl(
private fun handlePossibleRemoval(
key: String,
removed: MediaData,
- notificationRemoved: Boolean = false
+ notificationRemoved: Boolean = false,
) {
val hasSession = removed.token != null
if (hasSession && removed.semanticActions != null) {
@@ -1445,7 +1444,7 @@ class LegacyMediaDataManagerImpl(
Log.d(
TAG,
"Notification ($notificationRemoved) and/or session " +
- "($hasSession) gone for inactive player $key"
+ "($hasSession) gone for inactive player $key",
)
}
convertToResumePlayer(key, removed)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaActions.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaActions.kt
index f2825d0465ad..4f9791353b8a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaActions.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaActions.kt
@@ -16,6 +16,7 @@
package com.android.systemui.media.controls.domain.pipeline
+import android.annotation.WorkerThread
import android.app.ActivityOptions
import android.app.BroadcastOptions
import android.app.Notification
@@ -50,6 +51,7 @@ private const val TAG = "MediaActions"
* @return a Pair consisting of a list of media actions, and a list of ints representing which of
* those actions should be shown in the compact player
*/
+@WorkerThread
fun createActionsFromState(
context: Context,
packageName: String,
@@ -69,7 +71,7 @@ fun createActionsFromState(
context.getString(R.string.controls_media_button_connecting),
context.getDrawable(R.drawable.ic_media_connecting_container),
// Specify a rebind id to prevent the spinner from restarting on later binds.
- com.android.internal.R.drawable.progress_small_material
+ com.android.internal.R.drawable.progress_small_material,
)
} else if (isPlayingState(state.state)) {
getStandardAction(context, controller, state.actions, PlaybackState.ACTION_PAUSE)
@@ -128,7 +130,7 @@ fun createActionsFromState(
nextCustomAction(),
nextCustomAction(),
reserveNext,
- reservePrev
+ reservePrev,
)
}
@@ -146,7 +148,7 @@ private fun getStandardAction(
context: Context,
controller: MediaController,
stateActions: Long,
- @PlaybackState.Actions action: Long
+ @PlaybackState.Actions action: Long,
): MediaAction? {
if (!includesAction(stateActions, action)) {
return null
@@ -158,7 +160,7 @@ private fun getStandardAction(
context.getDrawable(R.drawable.ic_media_play),
{ controller.transportControls.play() },
context.getString(R.string.controls_media_button_play),
- context.getDrawable(R.drawable.ic_media_play_container)
+ context.getDrawable(R.drawable.ic_media_play_container),
)
}
PlaybackState.ACTION_PAUSE -> {
@@ -166,7 +168,7 @@ private fun getStandardAction(
context.getDrawable(R.drawable.ic_media_pause),
{ controller.transportControls.pause() },
context.getString(R.string.controls_media_button_pause),
- context.getDrawable(R.drawable.ic_media_pause_container)
+ context.getDrawable(R.drawable.ic_media_pause_container),
)
}
PlaybackState.ACTION_SKIP_TO_PREVIOUS -> {
@@ -174,7 +176,7 @@ private fun getStandardAction(
MediaControlDrawables.getPrevIcon(context),
{ controller.transportControls.skipToPrevious() },
context.getString(R.string.controls_media_button_prev),
- null
+ null,
)
}
PlaybackState.ACTION_SKIP_TO_NEXT -> {
@@ -182,7 +184,7 @@ private fun getStandardAction(
MediaControlDrawables.getNextIcon(context),
{ controller.transportControls.skipToNext() },
context.getString(R.string.controls_media_button_next),
- null
+ null,
)
}
else -> null
@@ -194,13 +196,13 @@ private fun getCustomAction(
context: Context,
packageName: String,
controller: MediaController,
- customAction: PlaybackState.CustomAction
+ customAction: PlaybackState.CustomAction,
): MediaAction {
return MediaAction(
Icon.createWithResource(packageName, customAction.icon).loadDrawable(context),
{ controller.transportControls.sendCustomAction(customAction, customAction.extras) },
customAction.name,
- null
+ null,
)
}
@@ -218,7 +220,7 @@ private fun includesAction(stateActions: Long, @PlaybackState.Actions action: Lo
/** Generate action buttons based on notification actions */
fun createActionsFromNotification(
context: Context,
- sbn: StatusBarNotification
+ sbn: StatusBarNotification,
): Pair<List<MediaNotificationAction>, List<Int>> {
val notif = sbn.notification
val actionIcons: MutableList<MediaNotificationAction> = ArrayList()
@@ -229,7 +231,7 @@ fun createActionsFromNotification(
if (actionsToShowCollapsed.size > MAX_COMPACT_ACTIONS) {
Log.e(
TAG,
- "Too many compact actions for ${sbn.key}, limiting to first $MAX_COMPACT_ACTIONS"
+ "Too many compact actions for ${sbn.key}, limiting to first $MAX_COMPACT_ACTIONS",
)
actionsToShowCollapsed = actionsToShowCollapsed.subList(0, MAX_COMPACT_ACTIONS)
}
@@ -239,7 +241,7 @@ fun createActionsFromNotification(
Log.w(
TAG,
"Too many notification actions for ${sbn.key}, " +
- "limiting to first $MAX_NOTIFICATION_ACTIONS"
+ "limiting to first $MAX_NOTIFICATION_ACTIONS",
)
}
@@ -253,7 +255,7 @@ fun createActionsFromNotification(
val themeText =
com.android.settingslib.Utils.getColorAttr(
context,
- com.android.internal.R.attr.textColorPrimary
+ com.android.internal.R.attr.textColorPrimary,
)
.defaultColor
@@ -271,7 +273,7 @@ fun createActionsFromNotification(
action.isAuthenticationRequired,
action.actionIntent,
mediaActionIcon,
- action.title
+ action.title,
)
actionIcons.add(mediaAction)
}
@@ -288,7 +290,7 @@ fun createActionsFromNotification(
*/
fun getNotificationActions(
actions: List<MediaNotificationAction>,
- activityStarter: ActivityStarter
+ activityStarter: ActivityStarter,
): List<MediaAction> {
return actions.map { action ->
val runnable =
@@ -303,7 +305,7 @@ fun getNotificationActions(
activityStarter.dismissKeyguardThenExecute(
{ sendPendingIntent(action.actionIntent) },
{},
- true
+ true,
)
else -> sendPendingIntent(actionIntent)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt
index 5f0a9f82b9ae..fd7b6dcfebbc 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt
@@ -119,7 +119,7 @@ private val ART_URIS =
arrayOf(
MediaMetadata.METADATA_KEY_ALBUM_ART_URI,
MediaMetadata.METADATA_KEY_ART_URI,
- MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI
+ MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI,
)
private const val TAG = "MediaDataProcessor"
@@ -177,7 +177,7 @@ class MediaDataProcessor(
private val themeText =
com.android.settingslib.Utils.getColorAttr(
context,
- com.android.internal.R.attr.textColorPrimary
+ com.android.internal.R.attr.textColorPrimary,
)
.defaultColor
@@ -365,7 +365,7 @@ class MediaDataProcessor(
secureSettings.getBoolForUser(
Settings.Secure.MEDIA_CONTROLS_RECOMMENDATION,
true,
- UserHandle.USER_CURRENT
+ UserHandle.USER_CURRENT,
)
useQsMediaPlayer && flag
@@ -386,7 +386,7 @@ class MediaDataProcessor(
if (!allowMediaRecommendations) {
dismissSmartspaceRecommendation(
key = mediaDataRepository.smartspaceMediaData.value.targetId,
- delay = 0L
+ delay = 0L,
)
}
}
@@ -413,7 +413,7 @@ class MediaDataProcessor(
token: MediaSession.Token,
appName: String,
appIntent: PendingIntent,
- packageName: String
+ packageName: String,
) {
// Resume controls don't have a notification key, so store by package name instead
if (!mediaDataRepository.mediaEntries.value.containsKey(packageName)) {
@@ -449,7 +449,7 @@ class MediaDataProcessor(
token,
appName,
appIntent,
- packageName
+ packageName,
)
}
} else {
@@ -461,7 +461,7 @@ class MediaDataProcessor(
token,
appName,
appIntent,
- packageName
+ packageName,
)
}
}
@@ -582,30 +582,37 @@ class MediaDataProcessor(
/** Called when the player's [PlaybackState] has been updated with new actions and/or state */
internal fun updateState(key: String, state: PlaybackState) {
mediaDataRepository.mediaEntries.value.get(key)?.let {
- val token = it.token
- if (token == null) {
- if (DEBUG) Log.d(TAG, "State updated, but token was null")
- return
- }
- val actions =
- createActionsFromState(
- it.packageName,
- mediaControllerFactory.create(it.token),
- UserHandle(it.userId)
- )
+ applicationScope.launch {
+ withContext(backgroundDispatcher) {
+ val token = it.token
+ if (token == null) {
+ if (DEBUG) Log.d(TAG, "State updated, but token was null")
+ return@withContext
+ }
+ val actions =
+ createActionsFromState(
+ it.packageName,
+ mediaControllerFactory.create(it.token),
+ UserHandle(it.userId),
+ )
- // Control buttons
- // If flag is enabled and controller has a PlaybackState,
- // create actions from session info
- // otherwise, no need to update semantic actions.
- val data =
- if (actions != null) {
- it.copy(semanticActions = actions, isPlaying = isPlayingState(state.state))
- } else {
- it.copy(isPlaying = isPlayingState(state.state))
+ // Control buttons
+ // If flag is enabled and controller has a PlaybackState,
+ // create actions from session info
+ // otherwise, no need to update semantic actions.
+ val data =
+ if (actions != null) {
+ it.copy(
+ semanticActions = actions,
+ isPlaying = isPlayingState(state.state),
+ )
+ } else {
+ it.copy(isPlaying = isPlayingState(state.state))
+ }
+ if (DEBUG) Log.d(TAG, "State updated outside of notification")
+ withContext(mainDispatcher) { onMediaDataLoaded(key, key, data) }
}
- if (DEBUG) Log.d(TAG, "State updated outside of notification")
- onMediaDataLoaded(key, key, data)
+ }
}
}
@@ -633,7 +640,7 @@ class MediaDataProcessor(
}
foregroundExecutor.executeDelayed(
{ removeEntry(key, userInitiated = userInitiated) },
- delayMs
+ delayMs,
)
return existed
}
@@ -657,7 +664,7 @@ class MediaDataProcessor(
if (mediaDataRepository.dismissSmartspaceRecommendation(key)) {
foregroundExecutor.executeDelayed(
{ notifySmartspaceMediaDataRemoved(key, immediately = true) },
- delay
+ delay,
)
}
}
@@ -677,7 +684,7 @@ class MediaDataProcessor(
token: MediaSession.Token,
appName: String,
appIntent: PendingIntent,
- packageName: String
+ packageName: String,
) =
withContext(backgroundDispatcher) {
val lastActive = systemClock.elapsedRealtime()
@@ -694,7 +701,7 @@ class MediaDataProcessor(
token,
appName,
appIntent,
- packageName
+ packageName,
)
if (result == null || desc.title.isNullOrBlank()) {
Log.d(TAG, "No MediaData result for resumption")
@@ -733,7 +740,7 @@ class MediaDataProcessor(
appUid = result.appUid,
isExplicit = result.isExplicit,
resumeProgress = result.resumeProgress,
- )
+ ),
)
}
}
@@ -746,7 +753,7 @@ class MediaDataProcessor(
token: MediaSession.Token,
appName: String,
appIntent: PendingIntent,
- packageName: String
+ packageName: String,
) {
if (desc.title.isNullOrBlank()) {
Log.e(TAG, "Description incomplete")
@@ -818,7 +825,7 @@ class MediaDataProcessor(
isExplicit = isExplicit,
resumeProgress = progress,
smartspaceId = SmallHash.hash(appUid + systemClock.currentTimeMillis().toInt()),
- )
+ ),
)
}
}
@@ -887,14 +894,14 @@ class MediaDataProcessor(
result.appUid,
sbn.packageName,
instanceId,
- result.playbackLocation
+ result.playbackLocation,
)
} else if (result.playbackLocation != oldEntry?.playbackLocation) {
logger.logPlaybackLocationChange(
result.appUid,
sbn.packageName,
instanceId,
- result.playbackLocation
+ result.playbackLocation,
)
}
@@ -911,7 +918,7 @@ class MediaDataProcessor(
val token =
sbn.notification.extras.getParcelable(
Notification.EXTRA_MEDIA_SESSION,
- MediaSession.Token::class.java
+ MediaSession.Token::class.java,
)
if (token == null) {
return
@@ -923,7 +930,7 @@ class MediaDataProcessor(
val appInfo =
notif.extras.getParcelable(
Notification.EXTRA_BUILDER_APPLICATION_INFO,
- ApplicationInfo::class.java
+ ApplicationInfo::class.java,
) ?: getAppInfoFromPackage(sbn.packageName)
// App name
@@ -987,7 +994,7 @@ class MediaDataProcessor(
val deviceIntent =
extras.getParcelable(
Notification.EXTRA_MEDIA_REMOTE_INTENT,
- PendingIntent::class.java
+ PendingIntent::class.java,
)
Log.d(TAG, "$key is RCN for $deviceName")
@@ -1003,7 +1010,7 @@ class MediaDataProcessor(
deviceDrawable,
deviceName,
deviceIntent,
- showBroadcastButton = false
+ showBroadcastButton = false,
)
}
}
@@ -1093,7 +1100,7 @@ class MediaDataProcessor(
mediaData.copy(
resumeAction = oldResumeAction,
hasCheckedForResume = oldHasCheckedForResume,
- active = oldActive
+ active = oldActive,
)
onMediaDataLoaded(key, oldKey, mediaData)
}
@@ -1102,7 +1109,7 @@ class MediaDataProcessor(
private fun logSingleVsMultipleMediaAdded(
appUid: Int,
packageName: String,
- instanceId: InstanceId
+ instanceId: InstanceId,
) {
if (mediaDataRepository.mediaEntries.value.size == 1) {
logger.logSingleMediaPlayerInCarousel(appUid, packageName, instanceId)
@@ -1151,7 +1158,7 @@ class MediaDataProcessor(
private fun createActionsFromState(
packageName: String,
controller: MediaController,
- user: UserHandle
+ user: UserHandle,
): MediaButton? {
if (!mediaFlags.areMediaSessionActionsEnabled(packageName, user)) {
return null
@@ -1189,7 +1196,7 @@ class MediaDataProcessor(
packageName,
ContentProvider.getUriWithoutUserId(uri),
Intent.FLAG_GRANT_READ_URI_PERMISSION,
- ContentProvider.getUserIdFromUri(uri, userId)
+ ContentProvider.getUserIdFromUri(uri, userId),
)
return loadBitmapFromUri(uri)
} catch (e: SecurityException) {
@@ -1226,7 +1233,7 @@ class MediaDataProcessor(
val scale =
MediaDataUtils.getScaleFactor(
APair(width, height),
- APair(artworkWidth, artworkHeight)
+ APair(artworkWidth, artworkHeight),
)
// Downscale if needed
@@ -1251,7 +1258,7 @@ class MediaDataProcessor(
.loadDrawable(context),
action,
context.getString(R.string.controls_media_resume),
- context.getDrawable(R.drawable.ic_media_play_container)
+ context.getDrawable(R.drawable.ic_media_play_container),
)
}
@@ -1291,7 +1298,7 @@ class MediaDataProcessor(
} else {
notifySmartspaceMediaDataRemoved(
smartspaceMediaData.targetId,
- immediately = false
+ immediately = false,
)
mediaDataRepository.setRecommendation(
SmartspaceMediaData(
@@ -1362,7 +1369,7 @@ class MediaDataProcessor(
private fun handlePossibleRemoval(
key: String,
removed: MediaData,
- notificationRemoved: Boolean = false
+ notificationRemoved: Boolean = false,
) {
val hasSession = removed.token != null
if (hasSession && removed.semanticActions != null) {
@@ -1387,7 +1394,7 @@ class MediaDataProcessor(
Log.d(
TAG,
"Notification ($notificationRemoved) and/or session " +
- "($hasSession) gone for inactive player $key"
+ "($hasSession) gone for inactive player $key",
)
}
convertToResumePlayer(key, removed)
@@ -1513,7 +1520,7 @@ class MediaDataProcessor(
data: MediaData,
immediately: Boolean = true,
receivedSmartspaceCardLatency: Int = 0,
- isSsReactivated: Boolean = false
+ isSsReactivated: Boolean = false,
) {}
/**
@@ -1526,7 +1533,7 @@ class MediaDataProcessor(
fun onSmartspaceMediaDataLoaded(
key: String,
data: SmartspaceMediaData,
- shouldPrioritize: Boolean = false
+ shouldPrioritize: Boolean = false,
) {}
/** Called whenever a previously existing Media notification was removed. */
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListener.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListener.kt
index 275f1eecd4db..39cedc36dbec 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListener.kt
@@ -128,7 +128,7 @@ constructor(
data: MediaData,
immediately: Boolean,
receivedSmartspaceCardLatency: Int,
- isSsReactivated: Boolean
+ isSsReactivated: Boolean,
) {
var reusedListener: PlaybackStateListener? = null
@@ -183,7 +183,7 @@ constructor(
override fun onSmartspaceMediaDataLoaded(
key: String,
data: SmartspaceMediaData,
- shouldPrioritize: Boolean
+ shouldPrioritize: Boolean,
) {
if (!mediaFlags.isPersistentSsCardEnabled()) return
@@ -259,7 +259,9 @@ constructor(
}
override fun onPlaybackStateChanged(state: PlaybackState?) {
- processState(state, dispatchEvents = true, currentResumption = resumption)
+ bgExecutor.execute {
+ processState(state, dispatchEvents = true, currentResumption = resumption)
+ }
}
override fun onSessionDestroyed() {
@@ -276,6 +278,7 @@ constructor(
}
}
+ @WorkerThread
private fun processState(
state: PlaybackState?,
dispatchEvents: Boolean,
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
index a0fb0bf25c7b..72650ea89dcb 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
@@ -16,6 +16,7 @@
package com.android.systemui.media.controls.ui.controller
+import android.annotation.WorkerThread
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
@@ -41,6 +42,7 @@ import com.android.internal.logging.InstanceId
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.Dumpable
+import com.android.systemui.Flags.mediaControlsUmoInflationInBackground
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
@@ -137,7 +139,7 @@ constructor(
private val activityStarter: ActivityStarter,
private val systemClock: SystemClock,
@Main private val mainDispatcher: CoroutineDispatcher,
- @Main executor: DelayableExecutor,
+ @Main private val uiExecutor: DelayableExecutor,
@Background private val bgExecutor: Executor,
@Background private val backgroundDispatcher: CoroutineDispatcher,
private val mediaManager: MediaDataManager,
@@ -227,7 +229,7 @@ constructor(
private var carouselLocale: Locale? = null
private val animationScaleObserver: ContentObserver =
- object : ContentObserver(executor, 0) {
+ object : ContentObserver(uiExecutor, 0) {
override fun onChange(selfChange: Boolean) {
if (!SceneContainerFlag.isEnabled) {
MediaPlayerData.players().forEach { it.updateAnimatorDurationScale() }
@@ -350,7 +352,7 @@ constructor(
MediaCarouselScrollHandler(
mediaCarousel,
pageIndicator,
- executor,
+ uiExecutor,
this::onSwipeToDismiss,
this::updatePageIndicatorLocation,
this::updateSeekbarListening,
@@ -458,7 +460,17 @@ constructor(
isSsReactivated: Boolean,
) {
debugLogger.logMediaLoaded(key, data.active)
- if (addOrUpdatePlayer(key, oldKey, data, isSsReactivated)) {
+ val onUiExecutionEnd =
+ if (mediaControlsUmoInflationInBackground()) {
+ Runnable {
+ if (immediately) {
+ updateHostVisibility()
+ }
+ }
+ } else {
+ null
+ }
+ if (addOrUpdatePlayer(key, oldKey, data, isSsReactivated, onUiExecutionEnd)) {
// Log card received if a new resumable media card is added
MediaPlayerData.getMediaPlayer(key)?.let {
logSmartspaceCardReported(
@@ -980,6 +992,7 @@ constructor(
oldKey: String?,
data: MediaData,
isSsReactivated: Boolean,
+ onUiExecutionEnd: Runnable? = null,
): Boolean =
traceSection("MediaCarouselController#addOrUpdatePlayer") {
MediaPlayerData.moveIfExists(oldKey, key)
@@ -987,76 +1000,119 @@ constructor(
val curVisibleMediaKey =
MediaPlayerData.visiblePlayerKeys()
.elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex)
- if (existingPlayer == null) {
- val newPlayer = mediaControlPanelFactory.get()
- if (SceneContainerFlag.isEnabled) {
- newPlayer.mediaViewController.widthInSceneContainerPx = widthInSceneContainerPx
- newPlayer.mediaViewController.heightInSceneContainerPx =
- heightInSceneContainerPx
- }
- newPlayer.attachPlayer(
- MediaViewHolder.create(LayoutInflater.from(context), mediaContent)
- )
- newPlayer.mediaViewController.sizeChangedListener = this::updateCarouselDimensions
- val lp =
- LinearLayout.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.WRAP_CONTENT,
- )
- newPlayer.mediaViewHolder?.player?.setLayoutParams(lp)
- newPlayer.bindPlayer(data, key)
- newPlayer.setListening(
- mediaCarouselScrollHandler.visibleToUser && currentlyExpanded
- )
- MediaPlayerData.addMediaPlayer(
- key,
- data,
- newPlayer,
- systemClock,
- isSsReactivated,
- debugLogger,
- )
- updateViewControllerToState(newPlayer.mediaViewController, noAnimation = true)
- // Media data added from a recommendation card should starts playing.
- if (
- (shouldScrollToKey && data.isPlaying == true) ||
- (!shouldScrollToKey && data.active)
- ) {
- reorderAllPlayers(curVisibleMediaKey, key)
+ if (mediaControlsUmoInflationInBackground()) {
+ if (existingPlayer == null) {
+ bgExecutor.execute {
+ val mediaViewHolder = createMediaViewHolderInBg()
+ // Add the new player in the main thread.
+ uiExecutor.execute {
+ setupNewPlayer(
+ key,
+ data,
+ isSsReactivated,
+ curVisibleMediaKey,
+ mediaViewHolder,
+ )
+ updatePageIndicator()
+ mediaCarouselScrollHandler.onPlayersChanged()
+ mediaFrame.requiresRemeasuring = true
+ onUiExecutionEnd?.run()
+ }
+ }
} else {
- needsReordering = true
+ updatePlayer(key, data, isSsReactivated, curVisibleMediaKey, existingPlayer)
+ updatePageIndicator()
+ mediaCarouselScrollHandler.onPlayersChanged()
+ mediaFrame.requiresRemeasuring = true
+ onUiExecutionEnd?.run()
}
} else {
- existingPlayer.bindPlayer(data, key)
- MediaPlayerData.addMediaPlayer(
- key,
- data,
- existingPlayer,
- systemClock,
- isSsReactivated,
- debugLogger,
- )
- val packageName = MediaPlayerData.smartspaceMediaData?.packageName ?: String()
- // In case of recommendations hits.
- // Check the playing status of media player and the package name.
- // To make sure we scroll to the right app's media player.
- if (
- isReorderingAllowed ||
- shouldScrollToKey &&
- data.isPlaying == true &&
- packageName == data.packageName
- ) {
- reorderAllPlayers(curVisibleMediaKey, key)
+ if (existingPlayer == null) {
+ val mediaViewHolder =
+ MediaViewHolder.create(LayoutInflater.from(context), mediaContent)
+ setupNewPlayer(key, data, isSsReactivated, curVisibleMediaKey, mediaViewHolder)
} else {
- needsReordering = true
+ updatePlayer(key, data, isSsReactivated, curVisibleMediaKey, existingPlayer)
}
+ updatePageIndicator()
+ mediaCarouselScrollHandler.onPlayersChanged()
+ mediaFrame.requiresRemeasuring = true
+ onUiExecutionEnd?.run()
}
- updatePageIndicator()
- mediaCarouselScrollHandler.onPlayersChanged()
- mediaFrame.requiresRemeasuring = true
return existingPlayer == null
}
+ private fun updatePlayer(
+ key: String,
+ data: MediaData,
+ isSsReactivated: Boolean,
+ curVisibleMediaKey: MediaPlayerData.MediaSortKey?,
+ existingPlayer: MediaControlPanel,
+ ) {
+ existingPlayer.bindPlayer(data, key)
+ MediaPlayerData.addMediaPlayer(
+ key,
+ data,
+ existingPlayer,
+ systemClock,
+ isSsReactivated,
+ debugLogger,
+ )
+ val packageName = MediaPlayerData.smartspaceMediaData?.packageName ?: String()
+ // In case of recommendations hits.
+ // Check the playing status of media player and the package name.
+ // To make sure we scroll to the right app's media player.
+ if (
+ isReorderingAllowed ||
+ shouldScrollToKey && data.isPlaying == true && packageName == data.packageName
+ ) {
+ reorderAllPlayers(curVisibleMediaKey, key)
+ } else {
+ needsReordering = true
+ }
+ }
+
+ private fun setupNewPlayer(
+ key: String,
+ data: MediaData,
+ isSsReactivated: Boolean,
+ curVisibleMediaKey: MediaPlayerData.MediaSortKey?,
+ mediaViewHolder: MediaViewHolder,
+ ) {
+ val newPlayer = mediaControlPanelFactory.get()
+ newPlayer.attachPlayer(mediaViewHolder)
+ newPlayer.mediaViewController.sizeChangedListener =
+ this@MediaCarouselController::updateCarouselDimensions
+ val lp =
+ LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ )
+ newPlayer.mediaViewHolder?.player?.setLayoutParams(lp)
+ newPlayer.bindPlayer(data, key)
+ newPlayer.setListening(mediaCarouselScrollHandler.visibleToUser && currentlyExpanded)
+ MediaPlayerData.addMediaPlayer(
+ key,
+ data,
+ newPlayer,
+ systemClock,
+ isSsReactivated,
+ debugLogger,
+ )
+ updateViewControllerToState(newPlayer.mediaViewController, noAnimation = true)
+ // Media data added from a recommendation card should starts playing.
+ if ((shouldScrollToKey && data.isPlaying == true) || (!shouldScrollToKey && data.active)) {
+ reorderAllPlayers(curVisibleMediaKey, key)
+ } else {
+ needsReordering = true
+ }
+ }
+
+ @WorkerThread
+ private fun createMediaViewHolderInBg(): MediaViewHolder {
+ return MediaViewHolder.create(LayoutInflater.from(context), mediaContent)
+ }
+
private fun addSmartspaceMediaRecommendations(
key: String,
data: SmartspaceMediaData,
@@ -1173,8 +1229,16 @@ constructor(
val previousVisibleKey =
MediaPlayerData.visiblePlayerKeys()
.elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex)
+ val onUiExecutionEnd = Runnable {
+ if (recreateMedia) {
+ reorderAllPlayers(previousVisibleKey)
+ }
+ }
- MediaPlayerData.mediaData().forEach { (key, data, isSsMediaRec) ->
+ val mediaDataList = MediaPlayerData.mediaData()
+ // Do not loop through the original list of media data because the re-addition of media data
+ // is being executed in background thread.
+ mediaDataList.forEach { (key, data, isSsMediaRec) ->
if (isSsMediaRec) {
val smartspaceMediaData = MediaPlayerData.smartspaceMediaData
removePlayer(key, dismissMediaData = false, dismissRecommendation = false)
@@ -1185,6 +1249,7 @@ constructor(
MediaPlayerData.shouldPrioritizeSs,
)
}
+ onUiExecutionEnd.run()
} else {
val isSsReactivated = MediaPlayerData.isSsReactivated(key)
if (recreateMedia) {
@@ -1195,11 +1260,9 @@ constructor(
oldKey = null,
data = data,
isSsReactivated = isSsReactivated,
+ onUiExecutionEnd = onUiExecutionEnd,
)
}
- if (recreateMedia) {
- reorderAllPlayers(previousVisibleKey)
- }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHostStatesManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHostStatesManager.kt
index 8660d12bcb85..782da4bd6a41 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHostStatesManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHostStatesManager.kt
@@ -17,6 +17,7 @@
package com.android.systemui.media.controls.ui.controller
import com.android.app.tracing.traceSection
+import com.android.systemui.Flags.mediaControlsUmoInflationInBackground
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.media.controls.ui.view.MediaHostState
import com.android.systemui.util.animation.MeasurementOutput
@@ -71,23 +72,34 @@ class MediaHostStatesManager @Inject constructor() {
*/
fun updateCarouselDimensions(
@MediaLocation location: Int,
- hostState: MediaHostState
+ hostState: MediaHostState,
): MeasurementOutput =
traceSection("MediaHostStatesManager#updateCarouselDimensions") {
val result = MeasurementOutput(0, 0)
+ var changed = false
for (controller in controllers) {
val measurement = controller.getMeasurementsForState(hostState)
measurement?.let {
if (it.measuredHeight > result.measuredHeight) {
result.measuredHeight = it.measuredHeight
+ changed = true
}
if (it.measuredWidth > result.measuredWidth) {
result.measuredWidth = it.measuredWidth
+ changed = true
}
}
}
- carouselSizes[location] = result
- return result
+ if (mediaControlsUmoInflationInBackground()) {
+ // Set carousel size if result measurements changed. This avoids setting carousel
+ // size when this method gets called before the addition of media view controllers
+ if (!carouselSizes.contains(location) || changed) {
+ carouselSizes[location] = result
+ }
+ } else {
+ carouselSizes[location] = result
+ }
+ return carouselSizes[location] ?: result
}
/** Add a callback to be called when a MediaState has updated */
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaHost.kt
index 09a618110f21..5ddc3470da43 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/view/MediaHost.kt
@@ -20,6 +20,7 @@ import android.graphics.Rect
import android.util.ArraySet
import android.view.View
import android.view.View.OnAttachStateChangeListener
+import com.android.systemui.Flags.mediaControlsUmoInflationInBackground
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.media.controls.shared.model.SmartspaceMediaData
@@ -91,8 +92,10 @@ class MediaHost(
data: MediaData,
immediately: Boolean,
receivedSmartspaceCardLatency: Int,
- isSsReactivated: Boolean
+ isSsReactivated: Boolean,
) {
+ if (mediaControlsUmoInflationInBackground()) return
+
if (immediately) {
updateViewVisibility()
}
@@ -101,7 +104,7 @@ class MediaHost(
override fun onSmartspaceMediaDataLoaded(
key: String,
data: SmartspaceMediaData,
- shouldPrioritize: Boolean
+ shouldPrioritize: Boolean,
) {
updateViewVisibility()
}
@@ -171,7 +174,7 @@ class MediaHost(
input.widthMeasureSpec =
View.MeasureSpec.makeMeasureSpec(
View.MeasureSpec.getSize(input.widthMeasureSpec),
- View.MeasureSpec.EXACTLY
+ View.MeasureSpec.EXACTLY,
)
}
// This will trigger a state change that ensures that we now have a state
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt
index 228b57603bed..d413474fde90 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt
@@ -54,6 +54,7 @@ import com.android.systemui.mediaprojection.MediaProjectionServiceHelper
import com.android.systemui.mediaprojection.appselector.data.RecentTask
import com.android.systemui.mediaprojection.appselector.view.MediaProjectionRecentsViewController
import com.android.systemui.res.R
+import com.android.systemui.shared.system.ActivityManagerWrapper
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.AsyncActivityLauncher
import java.lang.IllegalArgumentException
@@ -62,9 +63,10 @@ import javax.inject.Inject
class MediaProjectionAppSelectorActivity(
private val componentFactory: MediaProjectionAppSelectorComponent.Factory,
private val activityLauncher: AsyncActivityLauncher,
+ private val activityManager: ActivityManagerWrapper,
/** This is used to override the dependency in a screenshot test */
@VisibleForTesting
- private val listControllerFactory: ((userHandle: UserHandle) -> ResolverListController)?
+ private val listControllerFactory: ((userHandle: UserHandle) -> ResolverListController)?,
) :
ChooserActivity(),
MediaProjectionAppSelectorView,
@@ -74,8 +76,9 @@ class MediaProjectionAppSelectorActivity(
@Inject
constructor(
componentFactory: MediaProjectionAppSelectorComponent.Factory,
- activityLauncher: AsyncActivityLauncher
- ) : this(componentFactory, activityLauncher, listControllerFactory = null)
+ activityLauncher: AsyncActivityLauncher,
+ activityManager: ActivityManagerWrapper,
+ ) : this(componentFactory, activityLauncher, activityManager, listControllerFactory = null)
private val lifecycleRegistry = LifecycleRegistry(this)
override val lifecycle = lifecycleRegistry
@@ -100,7 +103,7 @@ class MediaProjectionAppSelectorActivity(
callingPackage = callingPackage,
view = this,
resultHandler = this,
- isFirstStart = savedInstanceState == null
+ isFirstStart = savedInstanceState == null,
)
component.lifecycleObservers.forEach { lifecycle.addObserver(it) }
@@ -113,7 +116,7 @@ class MediaProjectionAppSelectorActivity(
intent.configureChooserIntent(
resources,
component.hostUserHandle,
- component.personalProfileUserHandle
+ component.personalProfileUserHandle,
)
reviewGrantedConsentRequired =
@@ -180,7 +183,13 @@ class MediaProjectionAppSelectorActivity(
// is created and ready to be captured.
val activityStarted =
activityLauncher.startActivityAsUser(intent, userHandle, activityOptions.toBundle()) {
- returnSelectedApp(launchCookie, taskId = -1)
+ if (targetInfo.resolvedComponentName == callingActivity) {
+ // If attempting to launch the app used to launch the MediaProjection, then
+ // provide the task id since the launch cookie won't match the existing task
+ returnSelectedApp(launchCookie, taskId = activityManager.runningTask.taskId)
+ } else {
+ returnSelectedApp(launchCookie, taskId = -1)
+ }
}
// Rely on the ActivityManager to pop up a dialog regarding app suspension
@@ -213,7 +222,7 @@ class MediaProjectionAppSelectorActivity(
MediaProjectionServiceHelper.setReviewedConsentIfNeeded(
RECORD_CANCEL,
reviewGrantedConsentRequired,
- /* projection= */ null
+ /* projection= */ null,
)
if (isFinishing) {
// Only log dismissed when actually finishing, and not when changing configuration.
@@ -246,7 +255,7 @@ class MediaProjectionAppSelectorActivity(
val resultReceiver =
intent.getParcelableExtra(
EXTRA_CAPTURE_REGION_RESULT_RECEIVER,
- ResultReceiver::class.java
+ ResultReceiver::class.java,
) as ResultReceiver
val captureRegion = MediaProjectionCaptureTarget(launchCookie, taskId)
val data = Bundle().apply { putParcelable(KEY_CAPTURE_TARGET, captureRegion) }
@@ -260,8 +269,8 @@ class MediaProjectionAppSelectorActivity(
val mediaProjectionBinder = intent.getIBinderExtra(EXTRA_MEDIA_PROJECTION)
val projection = IMediaProjection.Stub.asInterface(mediaProjectionBinder)
- projection.setLaunchCookie(launchCookie)
- projection.setTaskId(taskId)
+ projection.launchCookie = launchCookie
+ projection.taskId = taskId
val intent = Intent()
intent.putExtra(EXTRA_MEDIA_PROJECTION, projection.asBinder())
@@ -270,7 +279,7 @@ class MediaProjectionAppSelectorActivity(
MediaProjectionServiceHelper.setReviewedConsentIfNeeded(
RECORD_CONTENT_TASK,
reviewGrantedConsentRequired,
- projection
+ projection,
)
}
@@ -457,7 +466,7 @@ class MediaProjectionAppSelectorActivity(
*/
private class RecyclerViewExpandingAccessibilityDelegate(
rdl: ResolverDrawerLayout,
- view: RecyclerView
+ view: RecyclerView,
) : RecyclerViewAccessibilityDelegate(view) {
private val delegate = AppListAccessibilityDelegate(rdl)
@@ -465,7 +474,7 @@ class MediaProjectionAppSelectorActivity(
override fun onRequestSendAccessibilityEvent(
host: ViewGroup,
child: View,
- event: AccessibilityEvent
+ event: AccessibilityEvent,
): Boolean {
super.onRequestSendAccessibilityEvent(host, child, event)
return delegate.onRequestSendAccessibilityEvent(host, child, event)
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
index 8351597f35de..c3729c0dcdfd 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
@@ -68,12 +68,12 @@ import com.android.systemui.res.R;
import com.android.systemui.statusbar.phone.AlertDialogWithDelegate;
import com.android.systemui.statusbar.phone.SystemUIDialog;
+import dagger.Lazy;
+
import java.util.function.Consumer;
import javax.inject.Inject;
-import dagger.Lazy;
-
public class MediaProjectionPermissionActivity extends Activity {
private static final String TAG = "MediaProjectionPermissionActivity";
private static final float MAX_APP_NAME_SIZE_PX = 500f;
@@ -132,8 +132,7 @@ public class MediaProjectionPermissionActivity extends Activity {
mPackageName = launchingIntent.getStringExtra(
EXTRA_PACKAGE_REUSING_GRANTED_CONSENT);
} else {
- setResult(RESULT_CANCELED);
- finish(RECORD_CANCEL, /* projection= */ null);
+ finishAsCancelled();
return;
}
}
@@ -145,8 +144,7 @@ public class MediaProjectionPermissionActivity extends Activity {
mUid = aInfo.uid;
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "Unable to look up package name", e);
- setResult(RESULT_CANCELED);
- finish(RECORD_CANCEL, /* projection= */ null);
+ finishAsCancelled();
return;
}
@@ -176,15 +174,13 @@ public class MediaProjectionPermissionActivity extends Activity {
}
} catch (RemoteException e) {
Log.e(TAG, "Error checking projection permissions", e);
- setResult(RESULT_CANCELED);
- finish(RECORD_CANCEL, /* projection= */ null);
+ finishAsCancelled();
return;
}
if (mFeatureFlags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES)) {
if (showScreenCaptureDisabledDialogIfNeeded()) {
- setResult(RESULT_CANCELED);
- finish(RECORD_CANCEL, /* projection= */ null);
+ finishAsCancelled();
return;
}
}
@@ -346,6 +342,21 @@ public class MediaProjectionPermissionActivity extends Activity {
private void requestDeviceUnlock() {
mKeyguardManager.requestDismissKeyguard(this,
new KeyguardManager.KeyguardDismissCallback() {
+
+ @Override
+ public void onDismissError() {
+ if (com.android.systemui.Flags.mediaProjectionDialogBehindLockscreen()) {
+ finishAsCancelled();
+ }
+ }
+
+ @Override
+ public void onDismissCancelled() {
+ if (com.android.systemui.Flags.mediaProjectionDialogBehindLockscreen()) {
+ finishAsCancelled();
+ }
+ }
+
@Override
public void onDismissSucceeded() {
mDialog.show();
@@ -386,8 +397,7 @@ public class MediaProjectionPermissionActivity extends Activity {
}
} catch (RemoteException e) {
Log.e(TAG, "Error granting projection permission", e);
- setResult(RESULT_CANCELED);
- finish(RECORD_CANCEL, /* projection= */ null);
+ finishAsCancelled();
} finally {
if (mDialog != null) {
mDialog.dismiss();
@@ -436,6 +446,14 @@ public class MediaProjectionPermissionActivity extends Activity {
}
}
+ /**
+ * Finishes this activity and cancel the projection request.
+ */
+ private void finishAsCancelled() {
+ setResult(RESULT_CANCELED);
+ finish(RECORD_CANCEL, /* projection= */ null);
+ }
+
@Nullable
private MediaProjectionConfig getMediaProjectionConfig() {
Intent intent = getIntent();
diff --git a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModel.kt
index 219e45c36b50..0e5404164ba1 100644
--- a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModel.kt
@@ -16,11 +16,19 @@
package com.android.systemui.notifications.ui.viewmodel
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.launch
/**
* Models UI state used to render the content of the notifications shade overlay.
@@ -33,10 +41,40 @@ class NotificationsShadeOverlayContentViewModel
constructor(
val shadeHeaderViewModelFactory: ShadeHeaderViewModel.Factory,
val notificationsPlaceholderViewModelFactory: NotificationsPlaceholderViewModel.Factory,
+ val sceneInteractor: SceneInteractor,
private val shadeInteractor: ShadeInteractor,
-) {
+) : ExclusiveActivatable() {
+
+ override suspend fun onActivated(): Nothing {
+ coroutineScope {
+ launch {
+ sceneInteractor.currentScene.collect { currentScene ->
+ when (currentScene) {
+ // TODO(b/369513770): The ShadeSession should be preserved in this scenario.
+ Scenes.Bouncer ->
+ shadeInteractor.collapseNotificationsShade(
+ loggingReason = "bouncer shown while shade is open"
+ )
+ }
+ }
+ }
+
+ launch {
+ shadeInteractor.isShadeTouchable
+ .distinctUntilChanged()
+ .filter { !it }
+ .collect {
+ shadeInteractor.collapseNotificationsShade(
+ loggingReason = "device became non-interactive"
+ )
+ }
+ }
+ }
+ awaitCancellation()
+ }
+
fun onScrimClicked() {
- shadeInteractor.collapseNotificationsShade(loggingReason = "Shade scrim clicked")
+ shadeInteractor.collapseNotificationsShade(loggingReason = "shade scrim clicked")
}
@AssistedFactory
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
index ba0d9384c7a4..66ac01ab95a0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
@@ -180,6 +180,7 @@ constructor(
qqsMediaHost.init(MediaHierarchyManager.LOCATION_QQS)
qsMediaHost.init(MediaHierarchyManager.LOCATION_QS)
setListenerCollections()
+ lifecycleScope.launch { viewModel.activate() }
}
override fun onCreateView(
@@ -331,7 +332,7 @@ constructor(
}
override fun setOverscrolling(overscrolling: Boolean) {
- viewModel.stackScrollerOverscrollingValue = overscrolling
+ viewModel.isStackScrollerOverscrolling = overscrolling
}
override fun setExpanded(qsExpanded: Boolean) {
@@ -410,11 +411,11 @@ constructor(
qsTransitionFraction: Float,
qsSquishinessFraction: Float,
) {
- super.setTransitionToFullShadeProgress(
- isTransitioningToFullShade,
- qsTransitionFraction,
- qsSquishinessFraction,
- )
+ viewModel.isTransitioningToFullShade = isTransitioningToFullShade
+ viewModel.lockscreenToShadeProgressValue = qsTransitionFraction
+ if (isTransitioningToFullShade) {
+ viewModel.squishinessFractionValue = qsSquishinessFraction
+ }
}
override fun setFancyClipping(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
index 7300ee1053ff..2d4e358414d5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
@@ -24,16 +24,19 @@ import androidx.lifecycle.LifecycleCoroutineScope
import com.android.systemui.Dumpable
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.qs.FooterActionsController
+import com.android.systemui.qs.composefragment.viewmodel.QSFragmentComposeViewModel.QSExpansionState
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
+import com.android.systemui.qs.panels.domain.interactor.TileSquishinessInteractor
import com.android.systemui.qs.ui.viewmodel.QuickSettingsContainerViewModel
import com.android.systemui.shade.LargeScreenHeaderHelper
import com.android.systemui.shade.transition.LargeScreenShadeInterpolator
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository
-import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.util.LargeScreenUtils
import com.android.systemui.util.asIndenting
import com.android.systemui.util.printSection
@@ -50,6 +53,7 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
@@ -61,13 +65,14 @@ constructor(
private val footerActionsViewModelFactory: FooterActionsViewModel.Factory,
private val footerActionsController: FooterActionsController,
private val sysuiStatusBarStateController: SysuiStatusBarStateController,
- private val keyguardBypassController: KeyguardBypassController,
+ private val deviceEntryInteractor: DeviceEntryInteractor,
private val disableFlagsRepository: DisableFlagsRepository,
private val largeScreenShadeInterpolator: LargeScreenShadeInterpolator,
private val configurationInteractor: ConfigurationInteractor,
private val largeScreenHeaderHelper: LargeScreenHeaderHelper,
+ private val squishinessInteractor: TileSquishinessInteractor,
@Assisted private val lifecycleScope: LifecycleCoroutineScope,
-) : Dumpable {
+) : Dumpable, ExclusiveActivatable() {
val footerActionsViewModel =
footerActionsViewModelFactory.create(lifecycleScope).also {
lifecycleScope.launch { footerActionsController.init() }
@@ -110,7 +115,7 @@ constructor(
_panelFraction.value = value
}
- private val _squishinessFraction = MutableStateFlow(0f)
+ private val _squishinessFraction = MutableStateFlow(1f)
var squishinessFractionValue: Float
get() = _squishinessFraction.value
set(value) {
@@ -131,7 +136,7 @@ constructor(
private val _headerAnimating = MutableStateFlow(false)
private val _stackScrollerOverscrolling = MutableStateFlow(false)
- var stackScrollerOverscrollingValue: Boolean
+ var isStackScrollerOverscrolling: Boolean
get() = _stackScrollerOverscrolling.value
set(value) {
_stackScrollerOverscrolling.value = value
@@ -150,8 +155,6 @@ constructor(
disableFlagsRepository.disableFlags.value.isQuickSettingsEnabled(),
)
- private val _showCollapsedOnKeyguard = MutableStateFlow(false)
-
private val _keyguardAndExpanded = MutableStateFlow(false)
/**
@@ -177,21 +180,65 @@ constructor(
awaitClose { sysuiStatusBarStateController.removeCallback(callback) }
}
+ .onStart { emit(sysuiStatusBarStateController.state) }
.stateIn(
lifecycleScope,
SharingStarted.WhileSubscribed(),
sysuiStatusBarStateController.state,
)
+ private val isKeyguardState =
+ statusBarState
+ .map { it == StatusBarState.KEYGUARD }
+ .stateIn(
+ lifecycleScope,
+ SharingStarted.WhileSubscribed(),
+ statusBarState.value == StatusBarState.KEYGUARD,
+ )
+
private val _viewHeight = MutableStateFlow(0)
private val _headerTranslation = MutableStateFlow(0f)
private val _inSplitShade = MutableStateFlow(false)
+ var isInSplitShade: Boolean
+ get() = _inSplitShade.value
+ set(value) {
+ _inSplitShade.value = value
+ }
private val _transitioningToFullShade = MutableStateFlow(false)
+ var isTransitioningToFullShade: Boolean
+ get() = _transitioningToFullShade.value
+ set(value) {
+ _transitioningToFullShade.value = value
+ }
- private val _lockscreenToShadeProgress = MutableStateFlow(false)
+ private val isBypassEnabled = deviceEntryInteractor.isBypassEnabled
+
+ private val showCollapsedOnKeyguard =
+ combine(
+ isBypassEnabled,
+ _transitioningToFullShade,
+ _inSplitShade,
+ ::calculateShowCollapsedOnKeyguard,
+ )
+ .stateIn(
+ lifecycleScope,
+ SharingStarted.WhileSubscribed(),
+ calculateShowCollapsedOnKeyguard(
+ isBypassEnabled.value,
+ isTransitioningToFullShade,
+ isInSplitShade,
+ ),
+ )
+
+ private val _lockscreenToShadeProgress = MutableStateFlow(0.0f)
+ var lockscreenToShadeProgressValue: Float
+ get() = _lockscreenToShadeProgress.value
+ set(value) {
+ _lockscreenToShadeProgress.value = value
+ }
private val _overscrolling = MutableStateFlow(false)
@@ -212,12 +259,32 @@ constructor(
_heightOverride.value = value
}
+ private val forceQS =
+ combine(
+ _qsExpanded,
+ _stackScrollerOverscrolling,
+ isKeyguardState,
+ showCollapsedOnKeyguard,
+ ::calculateForceQs,
+ )
+ .stateIn(
+ lifecycleScope,
+ SharingStarted.WhileSubscribed(),
+ calculateForceQs(
+ isQSExpanded,
+ isStackScrollerOverscrolling,
+ isKeyguardState.value,
+ showCollapsedOnKeyguard.value,
+ ),
+ )
+
val expansionState: StateFlow<QSExpansionState> =
- combine(_stackScrollerOverscrolling, _qsExpanded, _qsExpansion) { args: Array<Any> ->
- val expansion = args[2] as Float
- QSExpansionState(expansion.coerceIn(0f, 1f))
- }
- .stateIn(lifecycleScope, SharingStarted.WhileSubscribed(), QSExpansionState(0f))
+ combine(_qsExpansion, forceQS, ::calculateExpansionState)
+ .stateIn(
+ lifecycleScope,
+ SharingStarted.WhileSubscribed(),
+ calculateExpansionState(_qsExpansion.value, forceQS.value),
+ )
/**
* Accessibility action for collapsing/expanding QS. The provided runnable is responsible for
@@ -225,6 +292,16 @@ constructor(
*/
var collapseExpandAccessibilityAction: Runnable? = null
+ override suspend fun onActivated(): Nothing {
+ hydrateSquishinessInteractor()
+ }
+
+ private suspend fun hydrateSquishinessInteractor(): Nothing {
+ _squishinessFraction.collect {
+ squishinessInteractor.setSquishinessValue(it.constrainSquishiness())
+ }
+ }
+
override fun dump(pw: PrintWriter, args: Array<out String>) {
pw.asIndenting().run {
printSection("Quick Settings state") {
@@ -238,13 +315,17 @@ constructor(
println("panelExpansionFraction", panelExpansionFractionValue)
println("squishinessFraction", squishinessFractionValue)
println("expansionState", expansionState.value)
+ println("forceQS", forceQS.value)
}
printSection("Shade state") {
- println("stackOverscrolling", stackScrollerOverscrollingValue)
+ println("stackOverscrolling", isStackScrollerOverscrolling)
println("statusBarState", StatusBarState.toString(statusBarState.value))
+ println("isKeyguardState", isKeyguardState.value)
println("isSmallScreen", isSmallScreenValue)
println("heightOverride", "${heightOverrideValue}px")
println("qqsHeaderHeight", "${qqsHeaderHeight.value}px")
+ println("isSplitShade", isInSplitShade)
+ println("showCollapsedOnKeyguard", showCollapsedOnKeyguard.value)
}
}
}
@@ -257,3 +338,35 @@ constructor(
// In the future, this will have other relevant elements like squishiness.
data class QSExpansionState(@FloatRange(0.0, 1.0) val progress: Float)
}
+
+private fun Float.constrainSquishiness(): Float {
+ return (0.1f + this * 0.9f).coerceIn(0f, 1f)
+}
+
+// Helper methods for combining flows.
+
+private fun calculateExpansionState(expansion: Float, forceQs: Boolean): QSExpansionState {
+ return if (forceQs) {
+ QSExpansionState(1f)
+ } else {
+ QSExpansionState(expansion.coerceIn(0f, 1f))
+ }
+}
+
+private fun calculateForceQs(
+ isQSExpanded: Boolean,
+ isStackOverScrolling: Boolean,
+ isKeyguardShowing: Boolean,
+ shouldShowCollapsedOnKeyguard: Boolean,
+): Boolean {
+ return (isQSExpanded || isStackOverScrolling) &&
+ (isKeyguardShowing && !shouldShowCollapsedOnKeyguard)
+}
+
+private fun calculateShowCollapsedOnKeyguard(
+ isBypassEnabled: Boolean,
+ isTransitioningToFullShade: Boolean,
+ isInSplitShade: Boolean,
+): Boolean {
+ return isBypassEnabled || (isTransitioningToFullShade && !isInSplitShade)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
index 278352c6f69b..ead38f3f9b52 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt
@@ -33,6 +33,7 @@ import com.android.systemui.log.core.LogLevel.VERBOSE
import com.android.systemui.log.dagger.QSConfigLog
import com.android.systemui.log.dagger.QSLog
import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.plugins.qs.QSTile.State
import com.android.systemui.statusbar.StatusBarState
import com.google.errorprone.annotations.CompileTimeConstant
import javax.inject.Inject
@@ -57,6 +58,7 @@ constructor(
fun d(@CompileTimeConstant msg: String, arg: Any) {
buffer.log(TAG, DEBUG, { str1 = arg.toString() }, { "$msg: $str1" })
}
+
fun i(@CompileTimeConstant msg: String, arg: Any) {
buffer.log(TAG, INFO, { str1 = arg.toString() }, { "$msg: $str1" })
}
@@ -73,7 +75,19 @@ constructor(
str1 = tileSpec
str2 = reason
},
- { "[$str1] Tile destroyed. Reason: $str2" }
+ { "[$str1] Tile destroyed. Reason: $str2" },
+ )
+ }
+
+ fun logStateChanged(tileSpec: String, state: State) {
+ buffer.log(
+ TAG,
+ DEBUG,
+ {
+ str1 = tileSpec
+ str2 = state.toString()
+ },
+ { "[$str1] Tile state=$str2" },
)
}
@@ -85,7 +99,7 @@ constructor(
bool1 = listening
str1 = tileSpec
},
- { "[$str1] Tile listening=$bool1" }
+ { "[$str1] Tile listening=$bool1" },
)
}
@@ -98,7 +112,7 @@ constructor(
str1 = containerName
str2 = allSpecs
},
- { "Tiles listening=$bool1 in $str1. $str2" }
+ { "Tiles listening=$bool1 in $str1. $str2" },
)
}
@@ -112,7 +126,7 @@ constructor(
str2 = StatusBarState.toString(statusBarState)
str3 = toStateString(state)
},
- { "[$str1][$int1] Tile clicked. StatusBarState=$str2. TileState=$str3" }
+ { "[$str1][$int1] Tile clicked. StatusBarState=$str2. TileState=$str3" },
)
}
@@ -124,7 +138,7 @@ constructor(
str1 = tileSpec
int1 = eventId
},
- { "[$str1][$int1] Tile handling click." }
+ { "[$str1][$int1] Tile handling click." },
)
}
@@ -138,7 +152,7 @@ constructor(
str2 = StatusBarState.toString(statusBarState)
str3 = toStateString(state)
},
- { "[$str1][$int1] Tile secondary clicked. StatusBarState=$str2. TileState=$str3" }
+ { "[$str1][$int1] Tile secondary clicked. StatusBarState=$str2. TileState=$str3" },
)
}
@@ -150,7 +164,7 @@ constructor(
str1 = tileSpec
int1 = eventId
},
- { "[$str1][$int1] Tile handling secondary click." }
+ { "[$str1][$int1] Tile handling secondary click." },
)
}
@@ -164,7 +178,7 @@ constructor(
str2 = StatusBarState.toString(statusBarState)
str3 = toStateString(state)
},
- { "[$str1][$int1] Tile long clicked. StatusBarState=$str2. TileState=$str3" }
+ { "[$str1][$int1] Tile long clicked. StatusBarState=$str2. TileState=$str3" },
)
}
@@ -176,7 +190,7 @@ constructor(
str1 = tileSpec
int1 = eventId
},
- { "[$str1][$int1] Tile handling long click." }
+ { "[$str1][$int1] Tile handling long click." },
)
}
@@ -189,7 +203,7 @@ constructor(
int1 = lastType
str2 = callback
},
- { "[$str1] mLastTileState=$int1, Callback=$str2." }
+ { "[$str1] mLastTileState=$int1, Callback=$str2." },
)
}
@@ -198,7 +212,7 @@ constructor(
tileSpec: String,
state: Int,
disabledByPolicy: Boolean,
- color: Int
+ color: Int,
) {
// This method is added to further debug b/250618218 which has only been observed from the
// InternetTile, so we are only logging the background color change for the InternetTile
@@ -215,7 +229,7 @@ constructor(
bool1 = disabledByPolicy
int2 = color
},
- { "[$str1] state=$int1, disabledByPolicy=$bool1, color=$int2." }
+ { "[$str1] state=$int1, disabledByPolicy=$bool1, color=$int2." },
)
}
@@ -229,7 +243,7 @@ constructor(
str3 = state.icon?.toString()
int1 = state.state
},
- { "[$str1] Tile updated. Label=$str2. State=$int1. Icon=$str3." }
+ { "[$str1] Tile updated. Label=$str2. State=$int1. Icon=$str3." },
)
}
@@ -241,7 +255,7 @@ constructor(
str1 = containerName
bool1 = expanded
},
- { "$str1 expanded=$bool1" }
+ { "$str1 expanded=$bool1" },
)
}
@@ -253,7 +267,7 @@ constructor(
str1 = containerName
int1 = orientation
},
- { "onViewAttached: $str1 orientation $int1" }
+ { "onViewAttached: $str1 orientation $int1" },
)
}
@@ -265,7 +279,7 @@ constructor(
str1 = containerName
int1 = orientation
},
- { "onViewDetached: $str1 orientation $int1" }
+ { "onViewDetached: $str1 orientation $int1" },
)
}
@@ -276,7 +290,7 @@ constructor(
newShouldUseSplitShade: Boolean,
oldScreenLayout: Int,
newScreenLayout: Int,
- containerName: String
+ containerName: String,
) {
configChangedBuffer.log(
TAG,
@@ -297,7 +311,7 @@ constructor(
"screen layout=${toScreenLayoutString(long1.toInt())} " +
"(was ${toScreenLayoutString(long2.toInt())}), " +
"splitShade=$bool2 (was $bool1)"
- }
+ },
)
}
@@ -305,7 +319,7 @@ constructor(
after: Boolean,
before: Boolean,
force: Boolean,
- containerName: String
+ containerName: String,
) {
buffer.log(
TAG,
@@ -316,7 +330,7 @@ constructor(
bool2 = before
bool3 = force
},
- { "change tile layout: $str1 horizontal=$bool1 (was $bool2), force? $bool3" }
+ { "change tile layout: $str1 horizontal=$bool1 (was $bool2), force? $bool3" },
)
}
@@ -328,7 +342,7 @@ constructor(
int1 = tilesPerPageCount
int2 = totalTilesCount
},
- { "Distributing tiles: [tilesPerPageCount=$int1] [totalTilesCount=$int2]" }
+ { "Distributing tiles: [tilesPerPageCount=$int1] [totalTilesCount=$int2]" },
)
}
@@ -340,7 +354,7 @@ constructor(
str1 = tileName
int1 = pageIndex
},
- { "Adding $str1 to page number $int1" }
+ { "Adding $str1 to page number $int1" },
)
}
@@ -361,7 +375,7 @@ constructor(
str1 = viewName
str2 = toVisibilityString(visibility)
},
- { "$str1 visibility: $str2" }
+ { "$str1 visibility: $str2" },
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/TileSquishinessRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/TileSquishinessRepository.kt
new file mode 100644
index 000000000000..76ba9af2f475
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/TileSquishinessRepository.kt
@@ -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.systemui.qs.panels.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+@SysUISingleton
+class TileSquishinessRepository @Inject constructor() {
+ private val _squishiness = MutableStateFlow(1f)
+ val squishiness = _squishiness.asStateFlow()
+
+ fun setSquishinessValue(value: Float) {
+ _squishiness.value = value
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/TileSquishinessInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/TileSquishinessInteractor.kt
new file mode 100644
index 000000000000..4fdbc7647c78
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/TileSquishinessInteractor.kt
@@ -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.systemui.qs.panels.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.panels.data.repository.TileSquishinessRepository
+import javax.inject.Inject
+
+@SysUISingleton
+class TileSquishinessInteractor
+@Inject
+constructor(private val repository: TileSquishinessRepository) {
+ val squishiness = repository.squishiness
+
+ fun setSquishinessValue(value: Float) {
+ repository.setSquishinessValue(value)
+ }
+}
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 8998a7f5d815..a645b51404e7 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
@@ -41,6 +41,7 @@ fun SceneScope.QuickQuickSettings(
val sizedTiles by
viewModel.tileViewModels.collectAsStateWithLifecycle(initialValue = emptyList())
val tiles = sizedTiles.fastMap { it.tile }
+ val squishiness by viewModel.squishinessViewModel.squishiness.collectAsStateWithLifecycle()
DisposableEffect(tiles) {
val token = Any()
@@ -62,6 +63,7 @@ fun SceneScope.QuickQuickSettings(
tile = it.tile,
iconOnly = it.isIcon,
modifier = Modifier.element(it.tile.spec.toElementKey(spanIndex)),
+ squishiness = { squishiness },
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt
index 8c2fb252d13c..9ec5a82a18d7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt
@@ -35,6 +35,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.key
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
@@ -73,6 +74,7 @@ fun LargeTileContent(
secondaryLabel: String?,
icon: Icon,
colors: TileColors,
+ squishiness: () -> Float,
accessibilityUiState: AccessibilityUiState? = null,
toggleClickSupported: Boolean = false,
iconShape: Shape = RoundedCornerShape(CommonTileDefaults.InactiveCornerRadius),
@@ -89,6 +91,7 @@ fun LargeTileContent(
modifier =
Modifier.size(CommonTileDefaults.ToggleTargetSize).thenIf(toggleClickSupported) {
Modifier.clip(iconShape)
+ .verticalSquish(squishiness)
.background(colors.iconBackground, { 1f })
.combinedClickable(
onClick = onClick,
@@ -174,15 +177,17 @@ fun SmallTileContent(
} else if (icon is Icon.Resource) {
val image = AnimatedImageVector.animatedVectorResource(id = icon.res)
val painter =
- if (animateToEnd) {
- rememberAnimatedVectorPainter(animatedImageVector = image, atEnd = true)
- } else {
- var atEnd by remember(icon.res) { mutableStateOf(false) }
- LaunchedEffect(key1 = icon.res) {
- delay(350)
- atEnd = true
+ key(icon) {
+ if (animateToEnd) {
+ rememberAnimatedVectorPainter(animatedImageVector = image, atEnd = true)
+ } else {
+ var atEnd by remember(icon.res) { mutableStateOf(false) }
+ LaunchedEffect(key1 = icon.res) {
+ delay(350)
+ atEnd = true
+ }
+ rememberAnimatedVectorPainter(animatedImageVector = image, atEnd = atEnd)
}
- rememberAnimatedVectorPainter(animatedImageVector = image, atEnd = atEnd)
}
Image(
painter = painter,
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 e6edba513189..3ba49add530e 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
@@ -33,6 +33,7 @@ import com.android.systemui.qs.panels.ui.compose.rememberEditListState
import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
import com.android.systemui.qs.panels.ui.viewmodel.FixedColumnsSizeViewModel
import com.android.systemui.qs.panels.ui.viewmodel.IconTilesViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.TileSquishinessViewModel
import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.shared.ui.ElementKeys.toElementKey
@@ -45,6 +46,7 @@ class InfiniteGridLayout
constructor(
private val iconTilesViewModel: IconTilesViewModel,
private val gridSizeViewModel: FixedColumnsSizeViewModel,
+ private val squishinessViewModel: TileSquishinessViewModel,
) : PaginatableGridLayout {
@Composable
@@ -60,6 +62,7 @@ constructor(
}
val columns by gridSizeViewModel.columns.collectAsStateWithLifecycle()
val sizedTiles = tiles.map { SizedTileImpl(it, it.spec.width()) }
+ val squishiness by squishinessViewModel.squishiness.collectAsStateWithLifecycle()
VerticalSpannedGrid(
columns = columns,
@@ -72,6 +75,7 @@ constructor(
tile = it.tile,
iconOnly = iconTilesViewModel.isIconTile(it.tile.spec),
modifier = Modifier.element(it.tile.spec.toElementKey(spanIndex)),
+ squishiness = { squishiness },
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/SquishTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/SquishTile.kt
new file mode 100644
index 000000000000..ada1ef4de15d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/SquishTile.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.ui.compose.infinitegrid
+
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.layout
+import kotlin.math.roundToInt
+
+/**
+ * Modifier to squish the vertical bounds of a composable (usually a QS tile).
+ *
+ * It will squish the vertical bounds of the inner composable node by the value returned by
+ * [squishiness] on the measure/layout pass.
+ *
+ * The squished composable will be center aligned.
+ */
+fun Modifier.verticalSquish(squishiness: () -> Float): Modifier {
+ return layout { measurable, constraints ->
+ val placeable = measurable.measure(constraints)
+ val actualHeight = placeable.height
+ val squishedHeight = actualHeight * squishiness()
+ // Center the content by moving it UP (squishedHeight < actualHeight)
+ val scroll = (squishedHeight - actualHeight) / 2
+
+ layout(placeable.width, squishedHeight.roundToInt()) {
+ placeable.place(0, scroll.roundToInt())
+ }
+ }
+}
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 afcbed6db53b..4bd5b2d68c4c 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
@@ -98,7 +98,12 @@ fun TileLazyGrid(
}
@Composable
-fun Tile(tile: TileViewModel, iconOnly: Boolean, modifier: Modifier) {
+fun Tile(
+ tile: TileViewModel,
+ iconOnly: Boolean,
+ squishiness: () -> Float,
+ modifier: Modifier = Modifier,
+) {
val state by tile.state.collectAsStateWithLifecycle(tile.currentState)
val resources = resources()
val uiState = remember(state, resources) { state.toUiState(resources) }
@@ -119,6 +124,7 @@ fun Tile(tile: TileViewModel, iconOnly: Boolean, modifier: Modifier) {
onClick = tile::onClick,
onLongClick = tile::onLongClick,
uiState = uiState,
+ squishiness = squishiness,
modifier = modifier,
) { expandable ->
val icon = getTileIcon(icon = uiState.icon)
@@ -144,6 +150,7 @@ fun Tile(tile: TileViewModel, iconOnly: Boolean, modifier: Modifier) {
},
onLongClick = { tile.onLongClick(expandable) },
accessibilityUiState = uiState.accessibilityUiState,
+ squishiness = squishiness,
)
}
}
@@ -155,12 +162,17 @@ private fun TileContainer(
shape: Shape,
iconOnly: Boolean,
uiState: TileUiState,
+ squishiness: () -> Float,
modifier: Modifier = Modifier,
onClick: (Expandable) -> Unit = {},
onLongClick: (Expandable) -> Unit = {},
content: @Composable BoxScope.(Expandable) -> Unit,
) {
- Expandable(color = color, shape = shape, modifier = modifier.clip(shape)) {
+ Expandable(
+ color = color,
+ shape = shape,
+ modifier = modifier.clip(shape).verticalSquish(squishiness),
+ ) {
val longPressLabel = longPressLabel()
Box(
modifier =
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt
index eee905f9f894..88e3019ba163 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt
@@ -42,6 +42,7 @@ constructor(
tilesInteractor: CurrentTilesInteractor,
fixedColumnsSizeViewModel: FixedColumnsSizeViewModel,
quickQuickSettingsRowInteractor: QuickQuickSettingsRowInteractor,
+ val squishinessViewModel: TileSquishinessViewModel,
private val iconTilesViewModel: IconTilesViewModel,
@Application private val applicationScope: CoroutineScope,
) {
@@ -52,7 +53,7 @@ constructor(
quickQuickSettingsRowInteractor.rows.stateIn(
applicationScope,
SharingStarted.WhileSubscribed(),
- quickQuickSettingsRowInteractor.defaultRows
+ quickQuickSettingsRowInteractor.defaultRows,
)
val tileViewModels: StateFlow<List<SizedTile<TileViewModel>>> =
@@ -60,12 +61,7 @@ constructor(
.flatMapLatest { columns ->
tilesInteractor.currentTiles.combine(rows, ::Pair).mapLatest { (tiles, rows) ->
tiles
- .map {
- SizedTileImpl(
- TileViewModel(it.tile, it.spec),
- it.spec.width,
- )
- }
+ .map { SizedTileImpl(TileViewModel(it.tile, it.spec), it.spec.width) }
.let { splitInRowsSequence(it, columns).take(rows).toList().flatten() }
}
}
@@ -73,15 +69,10 @@ constructor(
applicationScope,
SharingStarted.WhileSubscribed(),
tilesInteractor.currentTiles.value
- .map {
- SizedTileImpl(
- TileViewModel(it.tile, it.spec),
- it.spec.width,
- )
- }
+ .map { SizedTileImpl(TileViewModel(it.tile, it.spec), it.spec.width) }
.let {
splitInRowsSequence(it, columns.value).take(rows.value).toList().flatten()
- }
+ },
)
private val TileSpec.width: Int
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileSquishinessViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileSquishinessViewModel.kt
new file mode 100644
index 000000000000..0c4d5de0edf9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileSquishinessViewModel.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.qs.panels.ui.viewmodel
+
+import com.android.systemui.qs.panels.domain.interactor.TileSquishinessInteractor
+import javax.inject.Inject
+
+/** View model to track the squishiness of tiles. */
+class TileSquishinessViewModel
+@Inject
+constructor(tileSquishinessInteractor: TileSquishinessInteractor) {
+ val squishiness = tileSquishinessInteractor.squishiness
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
index 5ea8c2183295..a4f3c7aa2652 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
@@ -14,6 +14,7 @@
package com.android.systemui.qs.tileimpl;
+import static com.android.systemui.Flags.qsNewTiles;
import static com.android.systemui.Flags.removeUpdateListenerInQsIconViewImpl;
import android.animation.Animator;
@@ -66,12 +67,22 @@ public class QSIconViewImpl extends QSIconView {
private ValueAnimator mColorAnimator = new ValueAnimator();
+ private int mColorUnavailable;
+ private int mColorInactive;
+ private int mColorActive;
+
public QSIconViewImpl(Context context) {
super(context);
final Resources res = context.getResources();
mIconSizePx = res.getDimensionPixelSize(R.dimen.qs_icon_size);
+ if (qsNewTiles()) { // pre-load icon tint colors
+ mColorUnavailable = Utils.getColorAttrDefaultColor(context, R.attr.outline);
+ mColorInactive = Utils.getColorAttrDefaultColor(context, R.attr.onShadeInactiveVariant);
+ mColorActive = Utils.getColorAttrDefaultColor(context, R.attr.onShadeActive);
+ }
+
mIcon = createIcon();
addView(mIcon);
mColorAnimator.setDuration(QS_ANIM_LENGTH);
@@ -195,7 +206,11 @@ public class QSIconViewImpl extends QSIconView {
}
protected int getColor(QSTile.State state) {
- return getIconColorForState(getContext(), state);
+ if (qsNewTiles()) {
+ return getCachedIconColorForState(state);
+ } else {
+ return getIconColorForState(getContext(), state);
+ }
}
private void animateGrayScale(int fromColor, int toColor, ImageView iv,
@@ -267,6 +282,19 @@ public class QSIconViewImpl extends QSIconView {
}
}
+ private int getCachedIconColorForState(QSTile.State state) {
+ if (state.disabledByPolicy || state.state == Tile.STATE_UNAVAILABLE) {
+ return mColorUnavailable;
+ } else if (state.state == Tile.STATE_INACTIVE) {
+ return mColorInactive;
+ } else if (state.state == Tile.STATE_ACTIVE) {
+ return mColorActive;
+ } else {
+ Log.e("QSIconView", "Invalid state " + state);
+ return 0;
+ }
+ }
+
private static class EndRunnableAnimatorListener extends AnimatorListenerAdapter {
private Runnable mRunnable;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index 4f3ea8331a17..18b1f071f44e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -643,7 +643,6 @@ constructor(
}
// HANDLE STATE CHANGES RELATED METHODS
-
protected open fun handleStateChanged(state: QSTile.State) {
val allowAnimations = animationsEnabled()
isClickable = state.state != Tile.STATE_UNAVAILABLE
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index 5ea9e6ae0a70..301ab2bcdd65 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -311,10 +311,7 @@ public class InternetDialogController implements AccessPointController.AccessPoi
mConfig = MobileMappings.Config.readConfig(mContext);
mTelephonyManager = mTelephonyManager.createForSubscriptionId(mDefaultDataSubId);
mSubIdTelephonyManagerMap.put(mDefaultDataSubId, mTelephonyManager);
- InternetTelephonyCallback telephonyCallback =
- new InternetTelephonyCallback(mDefaultDataSubId);
- mSubIdTelephonyCallbackMap.put(mDefaultDataSubId, telephonyCallback);
- mTelephonyManager.registerTelephonyCallback(mExecutor, telephonyCallback);
+ registerInternetTelephonyCallback(mTelephonyManager, mDefaultDataSubId);
// Listen the connectivity changes
mConnectivityManager.registerDefaultNetworkCallback(mConnectivityManagerNetworkCallback);
mCanConfigWifi = canConfigWifi;
@@ -346,6 +343,23 @@ public class InternetDialogController implements AccessPointController.AccessPoi
mCallback = null;
}
+ /**
+ * This is to generate and register the new callback to Telephony for uncached subscription id,
+ * then cache it. Telephony also cached this callback into
+ * {@link com.android.server.TelephonyRegistry}, so if subscription id and callback were cached
+ * already, it shall do nothing to avoid registering redundant callback to Telephony.
+ */
+ private void registerInternetTelephonyCallback(
+ TelephonyManager telephonyManager, int subId) {
+ if (mSubIdTelephonyCallbackMap.containsKey(subId)) {
+ // Avoid to generate and register unnecessary callback to Telephony.
+ return;
+ }
+ InternetTelephonyCallback telephonyCallback = new InternetTelephonyCallback(subId);
+ mSubIdTelephonyCallbackMap.put(subId, telephonyCallback);
+ telephonyManager.registerTelephonyCallback(mExecutor, telephonyCallback);
+ }
+
boolean isAirplaneModeEnabled() {
return mGlobalSettings.getInt(Settings.Global.AIRPLANE_MODE_ON, 0) != 0;
}
@@ -673,9 +687,7 @@ public class InternetDialogController implements AccessPointController.AccessPoi
int subId = subInfo.getSubscriptionId();
if (mSubIdTelephonyManagerMap.get(subId) == null) {
TelephonyManager secondaryTm = mTelephonyManager.createForSubscriptionId(subId);
- InternetTelephonyCallback telephonyCallback = new InternetTelephonyCallback(subId);
- secondaryTm.registerTelephonyCallback(mExecutor, telephonyCallback);
- mSubIdTelephonyCallbackMap.put(subId, telephonyCallback);
+ registerInternetTelephonyCallback(secondaryTm, subId);
mSubIdTelephonyManagerMap.put(subId, secondaryTm);
}
return subId;
@@ -1351,6 +1363,7 @@ public class InternetDialogController implements AccessPointController.AccessPoi
if (DEBUG) {
Log.d(TAG, "DDS: defaultDataSubId:" + defaultDataSubId);
}
+
if (SubscriptionManager.isUsableSubscriptionId(defaultDataSubId)) {
// clean up old defaultDataSubId
TelephonyCallback oldCallback = mSubIdTelephonyCallbackMap.get(mDefaultDataSubId);
@@ -1366,9 +1379,7 @@ public class InternetDialogController implements AccessPointController.AccessPoi
// create for new defaultDataSubId
mTelephonyManager = mTelephonyManager.createForSubscriptionId(defaultDataSubId);
mSubIdTelephonyManagerMap.put(defaultDataSubId, mTelephonyManager);
- InternetTelephonyCallback newCallback = new InternetTelephonyCallback(defaultDataSubId);
- mSubIdTelephonyCallbackMap.put(defaultDataSubId, newCallback);
- mTelephonyManager.registerTelephonyCallback(mHandler::post, newCallback);
+ registerInternetTelephonyCallback(mTelephonyManager, defaultDataSubId);
mCallback.onSubscriptionsChanged(defaultDataSubId);
}
mDefaultDataSubId = defaultDataSubId;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt
index 8965ef2bc493..bb0b9b7084fa 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt
@@ -18,7 +18,9 @@ package com.android.systemui.qs.tiles.impl.internet.domain
import android.content.Context
import android.content.res.Resources
+import android.os.Handler
import android.widget.Switch
+import com.android.settingslib.graph.SignalDrawable
import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.shared.model.Text.Companion.loadText
@@ -28,6 +30,7 @@ import com.android.systemui.qs.tiles.impl.internet.domain.model.InternetTileMode
import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
import com.android.systemui.qs.tiles.viewmodel.QSTileState
import com.android.systemui.res.R
+import com.android.systemui.statusbar.pipeline.shared.ui.model.InternetTileIconModel
import javax.inject.Inject
/** Maps [InternetTileModel] to [QSTileState]. */
@@ -37,6 +40,7 @@ constructor(
@Main private val resources: Resources,
private val theme: Resources.Theme,
private val context: Context,
+ @Main private val handler: Handler,
) : QSTileDataToStateMapper<InternetTileModel> {
override fun map(config: QSTileConfig, data: InternetTileModel): QSTileState =
@@ -44,25 +48,42 @@ constructor(
label = resources.getString(R.string.quick_settings_internet_label)
expandedAccessibilityClass = Switch::class
- if (data.secondaryLabel != null) {
- secondaryLabel = data.secondaryLabel.loadText(context)
- } else {
- secondaryLabel = data.secondaryTitle
- }
+ secondaryLabel =
+ if (data.secondaryLabel != null) {
+ data.secondaryLabel.loadText(context)
+ } else {
+ data.secondaryTitle
+ }
stateDescription = data.stateDescription.loadContentDescription(context)
contentDescription = data.contentDescription.loadContentDescription(context)
- iconRes = data.iconId
- if (data.icon != null) {
- this.icon = { data.icon }
- } else if (data.iconId != null) {
- val loadedIcon =
- Icon.Loaded(
- resources.getDrawable(data.iconId!!, theme),
- contentDescription = null
- )
- this.icon = { loadedIcon }
+ when (val dataIcon = data.icon) {
+ is InternetTileIconModel.ResourceId -> {
+ iconRes = dataIcon.resId
+ icon = {
+ Icon.Loaded(
+ resources.getDrawable(dataIcon.resId, theme),
+ contentDescription = null,
+ )
+ }
+ }
+
+ is InternetTileIconModel.Cellular -> {
+ val signalDrawable = SignalDrawable(context, handler)
+ signalDrawable.setLevel(dataIcon.level)
+ icon = { Icon.Loaded(signalDrawable, contentDescription = null) }
+ }
+
+ is InternetTileIconModel.Satellite -> {
+ iconRes = dataIcon.resourceIcon.res // level is inferred from res
+ icon = {
+ Icon.Loaded(
+ resources.getDrawable(dataIcon.resourceIcon.res, theme),
+ contentDescription = null,
+ )
+ }
+ }
}
sideViewIcon = QSTileState.SideViewIcon.Chevron
@@ -75,7 +96,7 @@ constructor(
setOf(
QSTileState.UserAction.CLICK,
QSTileState.UserAction.TOGGLE_CLICK,
- QSTileState.UserAction.LONG_CLICK
+ QSTileState.UserAction.LONG_CLICK,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractor.kt
index 204ead3fe29c..6fe3979fa446 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractor.kt
@@ -20,13 +20,10 @@ import android.annotation.StringRes
import android.content.Context
import android.os.UserHandle
import android.text.Html
-import com.android.settingslib.graph.SignalDrawable
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
-import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.shared.model.Text
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
import com.android.systemui.qs.tiles.impl.internet.domain.model.InternetTileModel
@@ -36,12 +33,12 @@ import com.android.systemui.statusbar.pipeline.ethernet.domain.EthernetInteracto
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
+import com.android.systemui.statusbar.pipeline.shared.ui.model.InternetTileIconModel
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
import com.android.systemui.utils.coroutines.flow.mapLatestConflated
import javax.inject.Inject
-import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
@@ -51,7 +48,6 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.withContext
@OptIn(ExperimentalCoroutinesApi::class)
/** Observes internet state changes providing the [InternetTileModel]. */
@@ -59,7 +55,6 @@ class InternetTileDataInteractor
@Inject
constructor(
private val context: Context,
- @Main private val mainCoroutineContext: CoroutineContext,
@Application private val scope: CoroutineScope,
airplaneModeRepository: AirplaneModeRepository,
private val connectivityRepository: ConnectivityRepository,
@@ -79,8 +74,7 @@ constructor(
flowOf(
InternetTileModel.Active(
secondaryTitle = secondary,
- iconId = wifiIcon.icon.res,
- icon = Icon.Loaded(context.getDrawable(wifiIcon.icon.res)!!, null),
+ icon = InternetTileIconModel.ResourceId(wifiIcon.icon.res),
stateDescription = wifiIcon.contentDescription,
contentDescription = ContentDescription.Loaded("$internetLabel,$secondary"),
)
@@ -116,11 +110,10 @@ constructor(
if (it == null) {
notConnectedFlow
} else {
- combine(
- it.networkName,
- it.signalLevelIcon,
- mobileDataContentName,
- ) { networkNameModel, signalIcon, dataContentDescription ->
+ combine(it.networkName, it.signalLevelIcon, mobileDataContentName) {
+ networkNameModel,
+ signalIcon,
+ dataContentDescription ->
Triple(networkNameModel, signalIcon, dataContentDescription)
}
.mapLatestConflated { (networkNameModel, signalIcon, dataContentDescription) ->
@@ -129,17 +122,12 @@ constructor(
val secondary =
mobileDataContentConcat(
networkNameModel.name,
- dataContentDescription
+ dataContentDescription,
)
- val drawable =
- withContext(mainCoroutineContext) { SignalDrawable(context) }
- drawable.setLevel(signalIcon.level)
- val loadedIcon = Icon.Loaded(drawable, null)
-
InternetTileModel.Active(
secondaryTitle = secondary,
- icon = loadedIcon,
+ icon = InternetTileIconModel.Cellular(signalIcon.level),
stateDescription =
ContentDescription.Loaded(secondary.toString()),
contentDescription = ContentDescription.Loaded(internetLabel),
@@ -150,9 +138,10 @@ constructor(
signalIcon.icon.contentDescription.loadContentDescription(
context
)
+
InternetTileModel.Active(
secondaryTitle = secondary,
- iconId = signalIcon.icon.res,
+ icon = InternetTileIconModel.Satellite(signalIcon.icon),
stateDescription = ContentDescription.Loaded(secondary),
contentDescription = ContentDescription.Loaded(internetLabel),
)
@@ -164,7 +153,7 @@ constructor(
private fun mobileDataContentConcat(
networkName: String?,
- dataContentDescription: CharSequence?
+ dataContentDescription: CharSequence?,
): CharSequence {
if (dataContentDescription == null) {
return networkName ?: ""
@@ -177,9 +166,9 @@ constructor(
context.getString(
R.string.mobile_carrier_text_format,
networkName,
- dataContentDescription
+ dataContentDescription,
),
- 0
+ 0,
)
}
@@ -199,7 +188,7 @@ constructor(
flowOf(
InternetTileModel.Active(
secondaryLabel = secondary?.toText(),
- iconId = it.res,
+ icon = InternetTileIconModel.ResourceId(it.res),
stateDescription = null,
contentDescription = secondary,
)
@@ -208,16 +197,18 @@ constructor(
}
private val notConnectedFlow: StateFlow<InternetTileModel> =
- combine(
- wifiInteractor.areNetworksAvailable,
- airplaneModeRepository.isAirplaneMode,
- ) { networksAvailable, isAirplaneMode ->
+ combine(wifiInteractor.areNetworksAvailable, airplaneModeRepository.isAirplaneMode) {
+ networksAvailable,
+ isAirplaneMode ->
when {
isAirplaneMode -> {
val secondary = context.getString(R.string.status_bar_airplane)
InternetTileModel.Inactive(
secondaryTitle = secondary,
- iconId = R.drawable.ic_qs_no_internet_unavailable,
+ icon =
+ InternetTileIconModel.ResourceId(
+ R.drawable.ic_qs_no_internet_unavailable
+ ),
stateDescription = null,
contentDescription = ContentDescription.Loaded(secondary),
)
@@ -227,10 +218,13 @@ constructor(
context.getString(R.string.quick_settings_networks_available)
InternetTileModel.Inactive(
secondaryTitle = secondary,
- iconId = R.drawable.ic_qs_no_internet_available,
+ icon =
+ InternetTileIconModel.ResourceId(
+ R.drawable.ic_qs_no_internet_available
+ ),
stateDescription = null,
contentDescription =
- ContentDescription.Loaded("$internetLabel,$secondary")
+ ContentDescription.Loaded("$internetLabel,$secondary"),
)
}
else -> {
@@ -248,7 +242,7 @@ constructor(
*/
override fun tileData(
user: UserHandle,
- triggers: Flow<DataUpdateTrigger>
+ triggers: Flow<DataUpdateTrigger>,
): Flow<InternetTileModel> =
connectivityRepository.defaultConnections.flatMapLatest {
when {
@@ -265,7 +259,7 @@ constructor(
val NOT_CONNECTED_NETWORKS_UNAVAILABLE =
InternetTileModel.Inactive(
secondaryLabel = Text.Resource(R.string.quick_settings_networks_unavailable),
- iconId = R.drawable.ic_qs_no_internet_unavailable,
+ icon = InternetTileIconModel.ResourceId(R.drawable.ic_qs_no_internet_unavailable),
stateDescription = null,
contentDescription =
ContentDescription.Resource(R.string.quick_settings_networks_unavailable),
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/model/InternetTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/model/InternetTileModel.kt
index ece904611782..15b4e472eec7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/model/InternetTileModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/model/InternetTileModel.kt
@@ -17,23 +17,21 @@
package com.android.systemui.qs.tiles.impl.internet.domain.model
import com.android.systemui.common.shared.model.ContentDescription
-import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.shared.model.Text
+import com.android.systemui.statusbar.pipeline.shared.ui.model.InternetTileIconModel
/** Model describing the state that the QS Internet tile should be in. */
sealed interface InternetTileModel {
val secondaryTitle: CharSequence?
val secondaryLabel: Text?
- val iconId: Int?
- val icon: Icon?
+ val icon: InternetTileIconModel
val stateDescription: ContentDescription?
val contentDescription: ContentDescription?
data class Active(
override val secondaryTitle: CharSequence? = null,
override val secondaryLabel: Text? = null,
- override val iconId: Int? = null,
- override val icon: Icon? = null,
+ override val icon: InternetTileIconModel = InternetTileIconModel.Cellular(1),
override val stateDescription: ContentDescription? = null,
override val contentDescription: ContentDescription? = null,
) : InternetTileModel
@@ -41,8 +39,7 @@ sealed interface InternetTileModel {
data class Inactive(
override val secondaryTitle: CharSequence? = null,
override val secondaryLabel: Text? = null,
- override val iconId: Int? = null,
- override val icon: Icon? = null,
+ override val icon: InternetTileIconModel = InternetTileIconModel.Cellular(1),
override val stateDescription: ContentDescription? = null,
override val contentDescription: ContentDescription? = null,
) : InternetTileModel
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModel.kt
index 7c8fbeaec0d5..afb9a788ec24 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModel.kt
@@ -16,10 +16,18 @@
package com.android.systemui.qs.ui.viewmodel
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.launch
/**
* Models UI state used to render the content of the quick settings shade overlay.
@@ -31,11 +39,42 @@ class QuickSettingsShadeOverlayContentViewModel
@AssistedInject
constructor(
val shadeInteractor: ShadeInteractor,
+ val sceneInteractor: SceneInteractor,
val shadeHeaderViewModelFactory: ShadeHeaderViewModel.Factory,
val quickSettingsContainerViewModel: QuickSettingsContainerViewModel,
-) {
+) : ExclusiveActivatable() {
+
+ override suspend fun onActivated(): Nothing {
+ coroutineScope {
+ launch {
+ sceneInteractor.currentScene.collect { currentScene ->
+ when (currentScene) {
+ // TODO(b/369513770): The ShadeSession should be preserved in this scenario.
+ Scenes.Bouncer ->
+ shadeInteractor.collapseQuickSettingsShade(
+ loggingReason = "bouncer shown while shade is open"
+ )
+ }
+ }
+ }
+
+ launch {
+ shadeInteractor.isShadeTouchable
+ .distinctUntilChanged()
+ .filter { !it }
+ .collect {
+ shadeInteractor.collapseQuickSettingsShade(
+ loggingReason = "device became non-interactive"
+ )
+ }
+ }
+ }
+
+ awaitCancellation()
+ }
+
fun onScrimClicked() {
- shadeInteractor.collapseQuickSettingsShade(loggingReason = "Shade scrim clicked")
+ shadeInteractor.collapseQuickSettingsShade(loggingReason = "shade scrim clicked")
}
@AssistedFactory
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
index a5f4a8959569..4d2bc91aa52a 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
@@ -61,11 +61,11 @@ constructor(
uiEventLogger,
notificationManager,
userContextProvider,
- keyguardDismissUtil
+ keyguardDismissUtil,
) {
- private val commandHandler =
- IssueRecordingServiceCommandHandler(
+ private val session =
+ IssueRecordingServiceSession(
bgExecutor,
dialogTransitionAnimator,
panelInteractor,
@@ -86,7 +86,7 @@ constructor(
Log.d(getTag(), "handling action: ${intent?.action}")
when (intent?.action) {
ACTION_START -> {
- commandHandler.handleStartCommand()
+ session.start()
if (!issueRecordingState.recordScreen) {
// If we don't want to record the screen, the ACTION_SHOW_START_NOTIF action
// will circumvent the RecordingService's screen recording start code.
@@ -94,12 +94,12 @@ constructor(
}
}
ACTION_STOP,
- ACTION_STOP_NOTIF -> commandHandler.handleStopCommand(contentResolver)
+ ACTION_STOP_NOTIF -> session.stop(contentResolver)
ACTION_SHARE -> {
- commandHandler.handleShareCommand(
+ session.share(
intent.getIntExtra(EXTRA_NOTIFICATION_ID, mNotificationId),
intent.getParcelableExtra(EXTRA_PATH, Uri::class.java),
- this
+ this,
)
// Unlike all other actions, action_share has different behavior for the screen
// recording qs tile than it does for the record issue qs tile. Return sticky to
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingServiceCommandHandler.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingServiceSession.kt
index 32de0f353502..e4d3e6cae502 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingServiceCommandHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingServiceSession.kt
@@ -34,9 +34,11 @@ private const val DISABLED = 0
/**
* This class exists to unit test the business logic encapsulated in IssueRecordingService. Android
* specifically calls out that there is no supported way to test IntentServices here:
- * https://developer.android.com/training/testing/other-components/services
+ * https://developer.android.com/training/testing/other-components/services, and mentions that the
+ * best way to add unit tests, is to introduce a separate class containing the business logic of
+ * that service, and test the functionality via that class.
*/
-class IssueRecordingServiceCommandHandler(
+class IssueRecordingServiceSession(
private val bgExecutor: Executor,
private val dialogTransitionAnimator: DialogTransitionAnimator,
private val panelInteractor: PanelInteractor,
@@ -47,12 +49,12 @@ class IssueRecordingServiceCommandHandler(
private val userContextProvider: UserContextProvider,
) {
- fun handleStartCommand() {
+ fun start() {
bgExecutor.execute { traceurMessageSender.startTracing(issueRecordingState.traceConfig) }
issueRecordingState.isRecording = true
}
- fun handleStopCommand(contentResolver: ContentResolver) {
+ fun stop(contentResolver: ContentResolver) {
bgExecutor.execute {
if (issueRecordingState.traceConfig.longTrace) {
Settings.Global.putInt(contentResolver, NOTIFY_SESSION_ENDED_SETTING, DISABLED)
@@ -62,12 +64,12 @@ class IssueRecordingServiceCommandHandler(
issueRecordingState.isRecording = false
}
- fun handleShareCommand(notificationId: Int, screenRecording: Uri?, context: Context) {
+ fun share(notificationId: Int, screenRecording: Uri?, context: Context) {
bgExecutor.execute {
notificationManager.cancelAsUser(
null,
notificationId,
- UserHandle(userContextProvider.userContext.userId)
+ UserHandle(userContextProvider.userContext.userId),
)
if (issueRecordingState.takeBugreport) {
diff --git a/packages/SystemUI/src/com/android/systemui/scene/data/model/SceneStack.kt b/packages/SystemUI/src/com/android/systemui/scene/data/model/SceneStack.kt
index 323bb3ddd63a..6479e577eb00 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/data/model/SceneStack.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/data/model/SceneStack.kt
@@ -21,10 +21,6 @@ import com.android.compose.animation.scene.SceneKey
/** An immutable stack of [SceneKey]s backed by a singly-linked list. */
sealed interface SceneStack
-private data object EmptyStack : SceneStack
-
-private data class StackedNodes(val head: SceneKey, val tail: SceneStack) : SceneStack
-
/** Returns the scene at the head of the stack, or `null` if empty. O(1) */
fun SceneStack.peek(): SceneKey? =
when (this) {
@@ -69,3 +65,14 @@ fun sceneStackOf(vararg scenes: SceneKey): SceneStack {
}
return result
}
+
+private data object EmptyStack : SceneStack {
+ override fun toString() = sceneStackToString()
+}
+
+private data class StackedNodes(val head: SceneKey, val tail: SceneStack) : SceneStack {
+ override fun toString() = sceneStackToString()
+}
+
+private fun SceneStack.sceneStackToString(): String =
+ asIterable().joinToString { it.testTag }.let { "SceneStack([$it])" }
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneBackInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneBackInteractor.kt
index afb72f03b28d..bebd398ac972 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneBackInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneBackInteractor.kt
@@ -19,7 +19,6 @@ package com.android.systemui.scene.domain.interactor
import com.android.compose.animation.scene.SceneKey
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.scene.data.model.SceneStack
-import com.android.systemui.scene.data.model.asIterable
import com.android.systemui.scene.data.model.peek
import com.android.systemui.scene.data.model.pop
import com.android.systemui.scene.data.model.push
@@ -68,13 +67,13 @@ constructor(
checkNotNull(stack.pop()) { "Cannot pop ${from.debugName} when stack is empty" }
}
}
- logger.logSceneBackStack(backStack.value.asIterable())
+ logger.logSceneBackStack(backStack.value)
}
/** Applies the given [transform] to the back stack. */
fun updateBackStack(transform: (SceneStack) -> SceneStack) {
_backStack.update { stack -> transform(stack) }
- logger.logSceneBackStack(backStack.value.asIterable())
+ logger.logSceneBackStack(backStack.value)
}
private fun stackOperation(from: SceneKey, to: SceneKey, stack: SceneStack): StackOperation? {
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt
index fb53ddb0ee7a..16c2ef556de8 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt
@@ -22,6 +22,7 @@ import com.android.compose.animation.scene.SceneKey
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.LogLevel
import com.android.systemui.log.dagger.SceneFrameworkLog
+import com.android.systemui.scene.data.model.SceneStack
import javax.inject.Inject
class SceneLogger @Inject constructor(@SceneFrameworkLog private val logBuffer: LogBuffer) {
@@ -40,16 +41,11 @@ class SceneLogger @Inject constructor(@SceneFrameworkLog private val logBuffer:
},
messagePrinter = {
"Scene framework is ${asWord(bool1)}${if (str1 != null) " $str1" else ""}"
- }
+ },
)
}
- fun logSceneChanged(
- from: SceneKey,
- to: SceneKey,
- reason: String,
- isInstant: Boolean,
- ) {
+ fun logSceneChanged(from: SceneKey, to: SceneKey, reason: String, isInstant: Boolean) {
logBuffer.log(
tag = TAG,
level = LogLevel.INFO,
@@ -123,11 +119,7 @@ class SceneLogger @Inject constructor(@SceneFrameworkLog private val logBuffer:
)
}
- fun logVisibilityChange(
- from: Boolean,
- to: Boolean,
- reason: String,
- ) {
+ fun logVisibilityChange(from: Boolean, to: Boolean, reason: String) {
fun asWord(isVisible: Boolean): String {
return if (isVisible) "visible" else "invisible"
}
@@ -144,9 +136,7 @@ class SceneLogger @Inject constructor(@SceneFrameworkLog private val logBuffer:
)
}
- fun logRemoteUserInputStarted(
- reason: String,
- ) {
+ fun logRemoteUserInputStarted(reason: String) {
logBuffer.log(
tag = TAG,
level = LogLevel.INFO,
@@ -164,11 +154,11 @@ class SceneLogger @Inject constructor(@SceneFrameworkLog private val logBuffer:
)
}
- fun logSceneBackStack(backStack: Iterable<SceneKey>) {
+ fun logSceneBackStack(backStack: SceneStack) {
logBuffer.log(
tag = TAG,
level = LogLevel.INFO,
- messageInitializer = { str1 = backStack.joinToString(", ") { it.debugName } },
+ messageInitializer = { str1 = backStack.toString() },
messagePrinter = { "back stack: $str1" },
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
index a7e7d8bb34dc..a8be5804d04a 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
@@ -108,7 +108,11 @@ object SceneWindowRootViewBinder {
traceName = "SceneWindowRootViewBinder",
minWindowLifecycleState = WindowLifecycleState.ATTACHED,
factory = {
- viewModelFactory.create(view.context.displayId, motionEventHandlerReceiver)
+ viewModelFactory.create(
+ view,
+ view.context.displayId,
+ motionEventHandlerReceiver,
+ )
},
) { viewModel ->
try {
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneUserActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneUserActionsViewModel.kt
index 5ff507a45d2e..fc172e8ca1d8 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneUserActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneUserActionsViewModel.kt
@@ -16,16 +16,13 @@
package com.android.systemui.scene.ui.viewmodel
-import com.android.compose.animation.scene.Edge
-import com.android.compose.animation.scene.Swipe
-import com.android.compose.animation.scene.SwipeDirection
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
-import com.android.systemui.scene.shared.model.Overlays
-import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.shared.model.ShadeMode
+import com.android.systemui.shade.ui.viewmodel.dualShadeActions
+import com.android.systemui.shade.ui.viewmodel.singleShadeActions
+import com.android.systemui.shade.ui.viewmodel.splitShadeActions
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@@ -36,41 +33,21 @@ constructor(private val shadeInteractor: ShadeInteractor) : UserActionsViewModel
override suspend fun hydrateActions(setActions: (Map<UserAction, UserActionResult>) -> Unit) {
shadeInteractor.shadeMode.collect { shadeMode ->
setActions(
- when (shadeMode) {
- ShadeMode.Single -> singleShadeActions()
- ShadeMode.Split -> splitShadeActions()
- ShadeMode.Dual -> dualShadeActions()
- }
+ buildList {
+ addAll(
+ when (shadeMode) {
+ ShadeMode.Single ->
+ singleShadeActions(requireTwoPointersForTopEdgeForQs = true)
+ ShadeMode.Split -> splitShadeActions()
+ ShadeMode.Dual -> dualShadeActions()
+ }
+ )
+ }
+ .associate { it }
)
}
}
- private fun singleShadeActions(): Map<UserAction, UserActionResult> {
- return mapOf(
- Swipe.Down to Scenes.Shade,
- swipeDownFromTopWithTwoFingers() to Scenes.QuickSettings,
- )
- }
-
- private fun splitShadeActions(): Map<UserAction, UserActionResult> {
- return mapOf(
- Swipe.Down to UserActionResult(Scenes.Shade, ToSplitShade),
- swipeDownFromTopWithTwoFingers() to UserActionResult(Scenes.Shade, ToSplitShade),
- )
- }
-
- private fun dualShadeActions(): Map<UserAction, UserActionResult> {
- return mapOf(
- Swipe.Down to Overlays.NotificationsShade,
- Swipe(direction = SwipeDirection.Down, fromSource = SceneContainerEdge.TopRight) to
- Overlays.QuickSettingsShade,
- )
- }
-
- private fun swipeDownFromTopWithTwoFingers(): UserAction {
- return Swipe(direction = SwipeDirection.Down, pointerCount = 2, fromSource = Edge.Top)
- }
-
@AssistedFactory
interface Factory {
fun create(): GoneUserActionsViewModel
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerHapticsViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerHapticsViewModel.kt
new file mode 100644
index 000000000000..4ef8e0fc3167
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerHapticsViewModel.kt
@@ -0,0 +1,90 @@
+/*
+ * 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.scene.ui.viewmodel
+
+import android.view.HapticFeedbackConstants
+import android.view.View
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.systemui.Flags
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.Overlays
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.google.android.msdl.data.model.MSDLToken
+import com.google.android.msdl.domain.MSDLPlayer
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+
+/**
+ * Models haptics UI state for the scene container.
+ *
+ * This model gets a [View] to play haptics using the [View.performHapticFeedback] API. This should
+ * be the only purpose of this reference.
+ */
+class SceneContainerHapticsViewModel
+@AssistedInject
+constructor(
+ @Assisted private val view: View,
+ sceneInteractor: SceneInteractor,
+ shadeInteractor: ShadeInteractor,
+ private val msdlPlayer: MSDLPlayer,
+) : ExclusiveActivatable() {
+
+ /** Should haptics be played by pulling down the shade */
+ private val isShadePullHapticsRequired: Flow<Boolean> =
+ combine(shadeInteractor.isUserInteracting, sceneInteractor.transitionState) {
+ interacting,
+ transitionState ->
+ interacting && transitionState.isValidForShadePullHaptics()
+ }
+ .distinctUntilChanged()
+
+ override suspend fun onActivated(): Nothing {
+ isShadePullHapticsRequired.collect { playShadePullHaptics ->
+ if (!playShadePullHaptics) return@collect
+
+ if (Flags.msdlFeedback()) {
+ msdlPlayer.playToken(MSDLToken.SWIPE_THRESHOLD_INDICATOR)
+ } else {
+ view.performHapticFeedback(HapticFeedbackConstants.GESTURE_START)
+ }
+ }
+ awaitCancellation()
+ }
+
+ private fun ObservableTransitionState.isValidForShadePullHaptics(): Boolean {
+ val validOrigin =
+ isTransitioning(from = Scenes.Gone) || isTransitioning(from = Scenes.Lockscreen)
+ val validDestination =
+ isTransitioning(to = Scenes.Shade) ||
+ isTransitioning(to = Scenes.QuickSettings) ||
+ isTransitioning(to = Overlays.QuickSettingsShade) ||
+ isTransitioning(to = Overlays.NotificationsShade)
+ return validOrigin && validDestination
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(view: View): SceneContainerHapticsViewModel
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
index 0bf2d499721b..f5053853846c 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
@@ -17,8 +17,10 @@
package com.android.systemui.scene.ui.viewmodel
import android.view.MotionEvent
+import android.view.View
import androidx.compose.runtime.getValue
import androidx.compose.ui.geometry.Offset
+import com.android.app.tracing.coroutines.launch
import com.android.compose.animation.scene.ContentKey
import com.android.compose.animation.scene.DefaultEdgeDetector
import com.android.compose.animation.scene.ObservableTransitionState
@@ -60,6 +62,8 @@ constructor(
private val splitEdgeDetector: SplitEdgeDetector,
private val logger: SceneLogger,
gestureFilterFactory: SceneContainerGestureFilter.Factory,
+ hapticsViewModelFactory: SceneContainerHapticsViewModel.Factory,
+ @Assisted view: View,
@Assisted displayId: Int,
@Assisted private val motionEventHandlerReceiver: (MotionEventHandler?) -> Unit,
) : ExclusiveActivatable() {
@@ -72,6 +76,8 @@ constructor(
/** Whether the container is visible. */
val isVisible: Boolean by hydrator.hydratedStateOf("isVisible", sceneInteractor.isVisible)
+ private val hapticsViewModel = hapticsViewModelFactory.create(view)
+
/**
* The [SwipeSourceDetector] to use for defining which edges of the screen can be defined in the
* [UserAction]s for this container.
@@ -107,6 +113,7 @@ constructor(
coroutineScope {
launch { hydrator.activate() }
launch { gestureFilter.activate() }
+ launch("SceneContainerHapticsViewModel") { hapticsViewModel.activate() }
}
awaitCancellation()
} finally {
@@ -281,6 +288,7 @@ constructor(
@AssistedFactory
interface Factory {
fun create(
+ view: View,
displayId: Int,
motionEventHandlerReceiver: (MotionEventHandler?) -> Unit,
): SceneContainerViewModel
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 0c05dbde6117..5896659e9898 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -23,6 +23,7 @@ import static com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE;
import static com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE;
import static com.android.keyguard.KeyguardClockSwitch.LARGE;
import static com.android.keyguard.KeyguardClockSwitch.SMALL;
+import static com.android.systemui.Flags.msdlFeedback;
import static com.android.systemui.Flags.predictiveBackAnimateShade;
import static com.android.systemui.Flags.smartspaceRelocateToBottom;
import static com.android.systemui.classifier.Classifier.BOUNCER_UNLOCK;
@@ -236,6 +237,9 @@ import com.android.wm.shell.animation.FlingAnimationUtils;
import dalvik.annotation.optimization.NeverCompile;
+import com.google.android.msdl.data.model.MSDLToken;
+import com.google.android.msdl.domain.MSDLPlayer;
+
import kotlin.Unit;
import kotlinx.coroutines.CoroutineDispatcher;
@@ -312,6 +316,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
private final StatusBarStateListener mStatusBarStateListener = new StatusBarStateListener();
private final NotificationPanelView mView;
private final VibratorHelper mVibratorHelper;
+ private final MSDLPlayer mMSDLPlayer;
private final MetricsLogger mMetricsLogger;
private final ConfigurationController mConfigurationController;
private final Provider<FlingAnimationUtils.Builder> mFlingAnimationUtilsBuilder;
@@ -777,7 +782,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
SplitShadeStateController splitShadeStateController,
PowerInteractor powerInteractor,
KeyguardClockPositionAlgorithm keyguardClockPositionAlgorithm,
- NaturalScrollingSettingObserver naturalScrollingSettingObserver) {
+ NaturalScrollingSettingObserver naturalScrollingSettingObserver,
+ MSDLPlayer msdlPlayer) {
SceneContainerFlag.assertInLegacyMode();
keyguardStateController.addCallback(new KeyguardStateController.Callback() {
@Override
@@ -855,6 +861,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
mNotificationsDragEnabled = mResources.getBoolean(
R.bool.config_enableNotificationShadeDrag);
mVibratorHelper = vibratorHelper;
+ mMSDLPlayer = msdlPlayer;
mVibrateOnOpening = mResources.getBoolean(R.bool.config_vibrateOnIconAnimation);
mStatusBarTouchableRegionManager = statusBarTouchableRegionManager;
mSystemClock = systemClock;
@@ -2911,7 +2918,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
}
if (!mStatusBarStateController.isDozing()) {
- mVibratorHelper.performHapticFeedback(mView, HapticFeedbackConstants.REJECT);
+ performHapticFeedback(HapticFeedbackConstants.REJECT);
}
}
@@ -3279,7 +3286,20 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
}
public void performHapticFeedback(int constant) {
- mVibratorHelper.performHapticFeedback(mView, constant);
+ if (msdlFeedback()) {
+ MSDLToken token;
+ switch (constant) {
+ case HapticFeedbackConstants.GESTURE_START ->
+ token = MSDLToken.SWIPE_THRESHOLD_INDICATOR;
+ case HapticFeedbackConstants.REJECT -> token = MSDLToken.FAILURE;
+ default -> token = null;
+ }
+ if (token != null) {
+ mMSDLPlayer.playToken(token, null);
+ }
+ } else {
+ mVibratorHelper.performHapticFeedback(mView, constant);
+ }
}
private class ShadeHeadsUpTrackerImpl implements ShadeHeadsUpTracker {
@@ -3736,10 +3756,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
private void maybeVibrateOnOpening(boolean openingWithTouch) {
if (mVibrateOnOpening && mBarState != KEYGUARD && mBarState != SHADE_LOCKED) {
if (!openingWithTouch || !mHasVibratedOnOpen) {
- mVibratorHelper.performHapticFeedback(
- mView,
- HapticFeedbackConstants.GESTURE_START
- );
+ performHapticFeedback(HapticFeedbackConstants.GESTURE_START);
mHasVibratedOnOpen = true;
mShadeLog.v("Vibrating on opening, mHasVibratedOnOpen=true");
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActions.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActions.kt
new file mode 100644
index 000000000000..65b6231705d4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActions.kt
@@ -0,0 +1,77 @@
+/*
+ * 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.ui.viewmodel
+
+import com.android.compose.animation.scene.Edge
+import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.SwipeDirection
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
+import com.android.systemui.scene.shared.model.Overlays
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade
+import com.android.systemui.scene.ui.viewmodel.SceneContainerEdge
+
+/** Returns collection of [UserAction] to [UserActionResult] pairs for opening the single shade. */
+fun singleShadeActions(
+ requireTwoPointersForTopEdgeForQs: Boolean = false
+): Array<Pair<UserAction, UserActionResult>> {
+ return arrayOf(
+ // Swiping down, not from the edge, always goes to shade.
+ Swipe.Down to Scenes.Shade,
+ swipeDown(pointerCount = 2) to Scenes.Shade,
+
+ // Swiping down from the top edge.
+ swipeDownFromTop(pointerCount = 1) to
+ if (requireTwoPointersForTopEdgeForQs) {
+ Scenes.Shade
+ } else {
+ Scenes.QuickSettings
+ },
+ swipeDownFromTop(pointerCount = 2) to Scenes.QuickSettings,
+ )
+}
+
+/** Returns collection of [UserAction] to [UserActionResult] pairs for opening the split shade. */
+fun splitShadeActions(): Array<Pair<UserAction, UserActionResult>> {
+ val splitShadeSceneKey = UserActionResult(Scenes.Shade, ToSplitShade)
+ return arrayOf(
+ // Swiping down, not from the edge, always goes to shade.
+ Swipe.Down to splitShadeSceneKey,
+ swipeDown(pointerCount = 2) to splitShadeSceneKey,
+ // Swiping down from the top edge goes to QS.
+ swipeDownFromTop(pointerCount = 1) to splitShadeSceneKey,
+ swipeDownFromTop(pointerCount = 2) to splitShadeSceneKey,
+ )
+}
+
+/** Returns collection of [UserAction] to [UserActionResult] pairs for opening the dual shade. */
+fun dualShadeActions(): Array<Pair<UserAction, UserActionResult>> {
+ return arrayOf(
+ Swipe.Down to Overlays.NotificationsShade,
+ Swipe(direction = SwipeDirection.Down, fromSource = SceneContainerEdge.TopRight) to
+ Overlays.QuickSettingsShade,
+ )
+}
+
+private fun swipeDownFromTop(pointerCount: Int): Swipe {
+ return Swipe(SwipeDirection.Down, fromSource = Edge.Top, pointerCount = pointerCount)
+}
+
+private fun swipeDown(pointerCount: Int): Swipe {
+ return Swipe(SwipeDirection.Down, pointerCount = pointerCount)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModel.kt
index f8a850a357f1..cc6e8c246ff7 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModel.kt
@@ -16,11 +16,13 @@
package com.android.systemui.shade.ui.viewmodel
+import com.android.app.tracing.coroutines.flow.map
import com.android.compose.animation.scene.Swipe
import com.android.compose.animation.scene.SwipeDirection
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.qs.ui.adapter.QSSceneAdapter
+import com.android.systemui.scene.domain.interactor.SceneBackInteractor
import com.android.systemui.scene.shared.model.SceneFamilies
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade
@@ -41,21 +43,23 @@ class ShadeUserActionsViewModel
constructor(
private val qsSceneAdapter: QSSceneAdapter,
private val shadeInteractor: ShadeInteractor,
+ private val sceneBackInteractor: SceneBackInteractor,
) : UserActionsViewModel() {
override suspend fun hydrateActions(setActions: (Map<UserAction, UserActionResult>) -> Unit) {
combine(
shadeInteractor.shadeMode,
qsSceneAdapter.isCustomizerShowing,
- ) { shadeMode, isCustomizerShowing ->
+ sceneBackInteractor.backScene.map { it ?: SceneFamilies.Home },
+ ) { shadeMode, isCustomizerShowing, backScene ->
buildMap<UserAction, UserActionResult> {
if (!isCustomizerShowing) {
set(
Swipe(SwipeDirection.Up),
UserActionResult(
- SceneFamilies.Home,
- ToSplitShade.takeIf { shadeMode is ShadeMode.Split }
- )
+ backScene,
+ ToSplitShade.takeIf { shadeMode is ShadeMode.Split },
+ ),
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index 8a6ec2aa27c9..5d14be8c974c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -37,6 +37,7 @@ import android.view.animation.Interpolator;
import androidx.annotation.NonNull;
import com.android.app.animation.Interpolators;
+import com.android.compose.animation.scene.OverlayKey;
import com.android.compose.animation.scene.SceneKey;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -60,6 +61,7 @@ import com.android.systemui.scene.domain.interactor.SceneBackInteractor;
import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor;
import com.android.systemui.scene.domain.interactor.SceneInteractor;
import com.android.systemui.scene.shared.flag.SceneContainerFlag;
+import com.android.systemui.scene.shared.model.Overlays;
import com.android.systemui.scene.shared.model.Scenes;
import com.android.systemui.shade.domain.interactor.ShadeInteractor;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
@@ -67,14 +69,12 @@ import com.android.systemui.statusbar.policy.CallbackController;
import com.android.systemui.util.Compile;
import com.android.systemui.util.kotlin.JavaAdapter;
-import com.google.common.base.Preconditions;
-
import dagger.Lazy;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Comparator;
-import java.util.Map;
+import java.util.Set;
import javax.inject.Inject;
@@ -230,6 +230,7 @@ public class StatusBarStateControllerImpl implements
combineFlows(
mDeviceUnlockedInteractorLazy.get().getDeviceUnlockStatus(),
mSceneInteractorLazy.get().getCurrentScene(),
+ mSceneInteractorLazy.get().getCurrentOverlays(),
mSceneBackInteractorLazy.get().getBackStack(),
mSceneContainerOcclusionInteractorLazy.get().getInvisibleDueToOcclusion(),
this::calculateStateFromSceneFramework),
@@ -690,19 +691,79 @@ public class StatusBarStateControllerImpl implements
private int calculateStateFromSceneFramework(
DeviceUnlockStatus deviceUnlockStatus,
SceneKey currentScene,
+ Set<OverlayKey> currentOverlays,
SceneStack backStack,
boolean isOccluded) {
SceneContainerFlag.isUnexpectedlyInLegacyMode();
- if (currentScene.equals(Scenes.Lockscreen)) {
- return StatusBarState.KEYGUARD;
- } else if (currentScene.equals(Scenes.Shade)
- && SceneStackKt.contains(backStack, Scenes.Lockscreen)) {
- return StatusBarState.SHADE_LOCKED;
- } else if (deviceUnlockStatus.isUnlocked() || isOccluded) {
- return StatusBarState.SHADE;
+
+ final boolean onBouncer = currentScene.equals(Scenes.Bouncer);
+ final boolean onCommunal = currentScene.equals(Scenes.Communal);
+ final boolean onGone = currentScene.equals(Scenes.Gone);
+ final boolean onLockscreen = currentScene.equals(Scenes.Lockscreen);
+ final boolean onQuickSettings = currentScene.equals(Scenes.QuickSettings);
+ final boolean onShade = currentScene.equals(Scenes.Shade);
+
+ final boolean overCommunal = SceneStackKt.contains(backStack, Scenes.Communal);
+ final boolean overLockscreen = SceneStackKt.contains(backStack, Scenes.Lockscreen);
+ final boolean overShade = SceneStackKt.contains(backStack, Scenes.Shade);
+
+ final boolean overlaidShade = currentOverlays.contains(Overlays.NotificationsShade);
+ final boolean overlaidQuickSettings = currentOverlays.contains(Overlays.QuickSettingsShade);
+
+ final boolean isUnlocked = deviceUnlockStatus.isUnlocked();
+
+ final String inputLogString = "currentScene=" + currentScene.getTestTag()
+ + " currentOverlays=" + currentOverlays + " backStack=" + backStack
+ + " isUnlocked=" + isUnlocked + " isOccluded=" + isOccluded;
+
+ int newState;
+
+ // When the device unlocks, several things happen 'at once':
+ // 1. deviceUnlockStatus.isUnlocked changes from false to true.
+ // 2. Lockscreen changes to Gone, either in currentScene or in backStack.
+ // 3. Bouncer is removed from currentScene or backStack, if it was present.
+ //
+ // From this function's perspective, though, deviceUnlockStatus, currentScene, and backStack
+ // each update separately, and the relative order of those updates is not well-defined. This
+ // doesn't work well for clients of this class (like remote input) that expect the device to
+ // be fully and properly unlocked when the state changes to SHADE.
+ //
+ // Therefore, we calculate the device to be in a locked-ish state (KEYGUARD or SHADE_LOCKED,
+ // but not SHADE) if *any* of these are still true:
+ // 1. deviceUnlockStatus.isUnlocked is false.
+ // 2. We are on (currentScene equals) a locked-ish scene (Lockscreen, Bouncer, or Communal).
+ // 3. We are over (backStack contains) a locked-ish scene (Lockscreen or Communal).
+
+ if (isOccluded) {
+ // Occlusion is special; even though the device is still technically on the lockscreen,
+ // the UI behaves as if it is unlocked.
+ newState = StatusBarState.SHADE;
+ } else if (onLockscreen || onBouncer || onCommunal || overLockscreen || overCommunal) {
+ // We get here if we are on or over a locked-ish scene, even if isUnlocked is true; we
+ // want to return SHADE_LOCKED or KEYGUARD until we are also neither on nor over a
+ // locked-ish scene.
+ if (onShade || onQuickSettings || overShade || overlaidShade || overlaidQuickSettings) {
+ newState = StatusBarState.SHADE_LOCKED;
+ } else {
+ newState = StatusBarState.KEYGUARD;
+ }
+ } else if (isUnlocked || onGone) {
+ newState = StatusBarState.SHADE;
+ } else if (onShade || onQuickSettings) {
+ // We get here if deviceUnlockStatus.isUnlocked is false but we are no longer on or over
+ // a locked-ish scene; we want to return SHADE_LOCKED until isUnlocked is also true.
+ newState = StatusBarState.SHADE_LOCKED;
} else {
- return Preconditions.checkNotNull(sStatusBarStateByLockedSceneKey.get(currentScene));
+ throw new IllegalArgumentException(
+ "unhandled input to calculateStateFromSceneFramework: " + inputLogString);
}
+
+ if (Compile.IS_DEBUG) {
+ Log.v(TAG, "calculateStateFromSceneFramework: "
+ + inputLogString + " -> " + StatusBarState.toString(newState));
+ }
+
+ return newState;
}
/** Notifies that the {@link StatusBarState} has changed to the given new state. */
@@ -716,15 +777,6 @@ public class StatusBarStateControllerImpl implements
updateStateAndNotifyListeners(newState);
}
- private static final Map<SceneKey, Integer> sStatusBarStateByLockedSceneKey = Map.of(
- Scenes.Lockscreen, StatusBarState.KEYGUARD,
- Scenes.Bouncer, StatusBarState.KEYGUARD,
- Scenes.Communal, StatusBarState.KEYGUARD,
- Scenes.Shade, StatusBarState.SHADE_LOCKED,
- Scenes.QuickSettings, StatusBarState.SHADE_LOCKED,
- Scenes.Gone, StatusBarState.SHADE
- );
-
/**
* For keeping track of our previous state to help with debugging
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/CommandQueueInitializer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/CommandQueueInitializer.kt
new file mode 100644
index 000000000000..57c8bc6133e6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/CommandQueueInitializer.kt
@@ -0,0 +1,114 @@
+/*
+ * 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.core
+
+import android.app.StatusBarManager
+import android.content.Context
+import android.os.Binder
+import android.os.RemoteException
+import android.view.WindowInsets
+import com.android.internal.statusbar.IStatusBarService
+import com.android.internal.statusbar.RegisterStatusBarResult
+import com.android.systemui.CoreStartable
+import com.android.systemui.InitController
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.navigationbar.NavigationBarController
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
+import dagger.Lazy
+import javax.inject.Inject
+
+@SysUISingleton
+class CommandQueueInitializer
+@Inject
+constructor(
+ private val context: Context,
+ private val commandQueue: CommandQueue,
+ private val commandQueueCallbacksLazy: Lazy<CommandQueue.Callbacks>,
+ private val statusBarModeRepository: StatusBarModeRepositoryStore,
+ private val initController: InitController,
+ private val barService: IStatusBarService,
+ private val navigationBarController: NavigationBarController,
+) : CoreStartable {
+
+ override fun start() {
+ StatusBarSimpleFragment.assertInNewMode()
+ val result: RegisterStatusBarResult =
+ try {
+ barService.registerStatusBar(commandQueue)
+ } catch (ex: RemoteException) {
+ ex.rethrowFromSystemServer()
+ return
+ }
+
+ createNavigationBar(result)
+
+ if ((result.mTransientBarTypes and WindowInsets.Type.statusBars()) != 0) {
+ statusBarModeRepository.defaultDisplay.showTransient()
+ }
+ val displayId = context.display.displayId
+ val commandQueueCallbacks = commandQueueCallbacksLazy.get()
+ commandQueueCallbacks.onSystemBarAttributesChanged(
+ displayId,
+ result.mAppearance,
+ result.mAppearanceRegions,
+ result.mNavbarColorManagedByIme,
+ result.mBehavior,
+ result.mRequestedVisibleTypes,
+ result.mPackageName,
+ result.mLetterboxDetails,
+ )
+
+ // StatusBarManagerService has a back up of IME token and it's restored here.
+ commandQueueCallbacks.setImeWindowStatus(
+ displayId,
+ result.mImeWindowVis,
+ result.mImeBackDisposition,
+ result.mShowImeSwitcher,
+ )
+
+ // Set up the initial icon state
+ val numIcons: Int = result.mIcons.size
+ for (i in 0 until numIcons) {
+ commandQueue.setIcon(result.mIcons.keyAt(i), result.mIcons.valueAt(i))
+ }
+
+ // set the initial view visibility
+ val disabledFlags1 = result.mDisabledFlags1
+ val disabledFlags2 = result.mDisabledFlags2
+ initController.addPostInitTask {
+ commandQueue.disable(displayId, disabledFlags1, disabledFlags2, /* animate= */ false)
+ try {
+ // NOTE(b/262059863): Force-update the disable flags after applying the flags
+ // returned from registerStatusBar(). The result's disabled flags may be stale
+ // if StatusBarManager's disabled flags are updated between registering the bar
+ // and this handling this post-init task. We force an update in this case, and use a
+ // new token to not conflict with any other disabled flags already requested by
+ // SysUI
+ val token = Binder()
+ barService.disable(StatusBarManager.DISABLE_HOME, token, context.packageName)
+ barService.disable(0, token, context.packageName)
+ } catch (ex: RemoteException) {
+ ex.rethrowFromSystemServer()
+ }
+ }
+ }
+
+ private fun createNavigationBar(result: RegisterStatusBarResult) {
+ navigationBarController.createNavigationBars(/* includeDefaultDisplay= */ true, result)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarConntectedDisplays.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarConntectedDisplays.kt
new file mode 100644
index 000000000000..54a18f764406
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarConntectedDisplays.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.statusbar.core
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the status bar connected displays flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object StatusBarConnectedDisplays {
+ /** The aconfig flag name */
+ const val FLAG_NAME = Flags.FLAG_STATUS_BAR_CONNECTED_DISPLAYS
+
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
+ /** Is the refactor enabled */
+ @JvmStatic
+ inline val isEnabled
+ get() = Flags.statusBarConnectedDisplays()
+
+ /**
+ * Called to ensure code is only run when the flag is enabled. This protects users from the
+ * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+ * build to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun isUnexpectedlyInLegacyMode() =
+ RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+ /**
+ * Called to ensure code is only run when the flag is enabled. This will throw an exception if
+ * the flag is not enabled to ensure that the refactor author catches issues in testing.
+ * Caution!! Using this check incorrectly will cause crashes in nextfood builds!
+ */
+ @JvmStatic
+ inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME)
+
+ /**
+ * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+ * the flag is enabled to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarOrchestrator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarOrchestrator.kt
new file mode 100644
index 000000000000..8bd990b83a63
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarOrchestrator.kt
@@ -0,0 +1,248 @@
+/*
+ * 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.core
+
+import android.view.View
+import com.android.systemui.CoreStartable
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.demomode.DemoModeController
+import com.android.systemui.plugins.DarkIconDispatcher
+import com.android.systemui.plugins.PluginDependencyProvider
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.shade.NotificationShadeWindowViewController
+import com.android.systemui.shade.ShadeSurface
+import com.android.systemui.statusbar.AutoHideUiElement
+import com.android.systemui.statusbar.NotificationRemoteInputManager
+import com.android.systemui.statusbar.data.model.StatusBarMode
+import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
+import com.android.systemui.statusbar.phone.AutoHideController
+import com.android.systemui.statusbar.phone.CentralSurfaces
+import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions
+import com.android.systemui.statusbar.phone.PhoneStatusBarViewController
+import com.android.systemui.statusbar.window.StatusBarWindowController
+import com.android.systemui.statusbar.window.data.model.StatusBarWindowState
+import com.android.systemui.statusbar.window.data.repository.StatusBarWindowStateRepositoryStore
+import com.android.wm.shell.bubbles.Bubbles
+import dagger.Lazy
+import java.io.PrintWriter
+import java.util.Optional
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChangedBy
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.launch
+
+/**
+ * Class responsible for managing the lifecycle and state of the status bar.
+ *
+ * It is a temporary class, created to pull status bar related logic out of CentralSurfacesImpl. The
+ * plan is break it out into individual classes.
+ */
+@SysUISingleton
+class StatusBarOrchestrator
+@Inject
+constructor(
+ @Application private val applicationScope: CoroutineScope,
+ private val statusBarInitializer: StatusBarInitializer,
+ private val statusBarWindowController: StatusBarWindowController,
+ private val statusBarModeRepository: StatusBarModeRepositoryStore,
+ private val demoModeController: DemoModeController,
+ private val pluginDependencyProvider: PluginDependencyProvider,
+ private val autoHideController: AutoHideController,
+ private val remoteInputManager: NotificationRemoteInputManager,
+ private val notificationShadeWindowViewControllerLazy:
+ Lazy<NotificationShadeWindowViewController>,
+ private val shadeSurface: ShadeSurface,
+ private val bubblesOptional: Optional<Bubbles>,
+ private val statusBarWindowStateRepositoryStore: StatusBarWindowStateRepositoryStore,
+ powerInteractor: PowerInteractor,
+ primaryBouncerInteractor: PrimaryBouncerInteractor,
+) : CoreStartable {
+
+ private val phoneStatusBarViewController =
+ MutableStateFlow<PhoneStatusBarViewController?>(value = null)
+
+ private val phoneStatusBarTransitions =
+ MutableStateFlow<PhoneStatusBarTransitions?>(value = null)
+
+ private val shouldAnimateNextBarModeChange =
+ combine(
+ statusBarModeRepository.defaultDisplay.isTransientShown,
+ powerInteractor.isAwake,
+ statusBarWindowStateRepositoryStore.defaultDisplay.windowState,
+ ) { isTransientShown, isDeviceAwake, statusBarWindowState ->
+ !isTransientShown &&
+ isDeviceAwake &&
+ statusBarWindowState != StatusBarWindowState.Hidden
+ }
+
+ private val controllerAndBouncerShowing =
+ combine(
+ phoneStatusBarViewController.filterNotNull(),
+ primaryBouncerInteractor.isShowing,
+ ::Pair,
+ )
+
+ private val barTransitionsAndDeviceAsleep =
+ combine(phoneStatusBarTransitions.filterNotNull(), powerInteractor.isAsleep, ::Pair)
+
+ private val statusBarVisible =
+ combine(
+ statusBarModeRepository.defaultDisplay.statusBarMode,
+ statusBarWindowStateRepositoryStore.defaultDisplay.windowState,
+ ) { mode, statusBarWindowState ->
+ mode != StatusBarMode.LIGHTS_OUT &&
+ mode != StatusBarMode.LIGHTS_OUT_TRANSPARENT &&
+ statusBarWindowState != StatusBarWindowState.Hidden
+ }
+
+ private val barModeUpdate =
+ combine(
+ shouldAnimateNextBarModeChange,
+ phoneStatusBarTransitions.filterNotNull(),
+ statusBarModeRepository.defaultDisplay.statusBarMode,
+ ::Triple,
+ )
+ .distinctUntilChangedBy { (_, barTransitions, statusBarMode) ->
+ // We only want to collect when either bar transitions or status bar mode
+ // changed.
+ Pair(barTransitions, statusBarMode)
+ }
+
+ override fun start() {
+ StatusBarSimpleFragment.assertInNewMode()
+ applicationScope.launch {
+ launch {
+ controllerAndBouncerShowing.collect { (controller, bouncerShowing) ->
+ setBouncerShowingForStatusBarComponents(controller, bouncerShowing)
+ }
+ }
+ launch {
+ barTransitionsAndDeviceAsleep.collect { (barTransitions, deviceAsleep) ->
+ if (deviceAsleep) {
+ barTransitions.finishAnimations()
+ }
+ }
+ }
+ launch { statusBarVisible.collect { updateBubblesVisibility(it) } }
+ launch {
+ barModeUpdate.collect { (animate, barTransitions, statusBarMode) ->
+ updateBarMode(animate, barTransitions, statusBarMode)
+ }
+ }
+ }
+ createAndAddWindow()
+ setupPluginDependencies()
+ setUpAutoHide()
+ }
+
+ private fun createAndAddWindow() {
+ initializeStatusBarFragment()
+ statusBarWindowController.attach()
+ }
+
+ private fun initializeStatusBarFragment() {
+ statusBarInitializer.statusBarViewUpdatedListener =
+ object : StatusBarInitializer.OnStatusBarViewUpdatedListener {
+ override fun onStatusBarViewUpdated(
+ statusBarViewController: PhoneStatusBarViewController,
+ statusBarTransitions: PhoneStatusBarTransitions,
+ ) {
+ phoneStatusBarViewController.value = statusBarViewController
+ phoneStatusBarTransitions.value = statusBarTransitions
+
+ notificationShadeWindowViewControllerLazy
+ .get()
+ .setStatusBarViewController(statusBarViewController)
+ // Ensure we re-propagate panel expansion values to the panel controller and
+ // any listeners it may have, such as PanelBar. This will also ensure we
+ // re-display the notification panel if necessary (for example, if
+ // a heads-up notification was being displayed and should continue being
+ // displayed).
+ shadeSurface.updateExpansionAndVisibility()
+ }
+ }
+ }
+
+ private fun setupPluginDependencies() {
+ pluginDependencyProvider.allowPluginDependency(DarkIconDispatcher::class.java)
+ pluginDependencyProvider.allowPluginDependency(StatusBarStateController::class.java)
+ }
+
+ private fun setUpAutoHide() {
+ autoHideController.setStatusBar(
+ object : AutoHideUiElement {
+ override fun synchronizeState() {}
+
+ override fun shouldHideOnTouch(): Boolean {
+ return !remoteInputManager.isRemoteInputActive
+ }
+
+ override fun isVisible(): Boolean {
+ return statusBarModeRepository.defaultDisplay.isTransientShown.value
+ }
+
+ override fun hide() {
+ statusBarModeRepository.defaultDisplay.clearTransient()
+ }
+ })
+ }
+
+ private fun updateBarMode(
+ animate: Boolean,
+ barTransitions: PhoneStatusBarTransitions,
+ barMode: StatusBarMode,
+ ) {
+ if (!demoModeController.isInDemoMode) {
+ barTransitions.transitionTo(barMode.toTransitionModeInt(), animate)
+ }
+ autoHideController.touchAutoHide()
+ }
+
+ private fun updateBubblesVisibility(statusBarVisible: Boolean) {
+ bubblesOptional.ifPresent { bubbles: Bubbles ->
+ bubbles.onStatusBarVisibilityChanged(statusBarVisible)
+ }
+ }
+
+ private fun setBouncerShowingForStatusBarComponents(
+ controller: PhoneStatusBarViewController,
+ bouncerShowing: Boolean,
+ ) {
+ val importance =
+ if (bouncerShowing) {
+ View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+ } else {
+ View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
+ }
+ controller.setImportantForAccessibility(importance)
+ }
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.println(statusBarWindowStateRepositoryStore.defaultDisplay.windowState.value)
+ CentralSurfaces.dumpBarTransitions(
+ pw,
+ "PhoneStatusBarTransitions",
+ phoneStatusBarTransitions.value,
+ )
+ }
+}
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 3903ff3c2b9b..cf238d553225 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
@@ -46,6 +46,7 @@ import dagger.multibindings.IntoMap
*/
@Module(includes = [StatusBarDataLayerModule::class, SystemBarUtilsProxyImpl.Module::class])
abstract class StatusBarModule {
+
@Binds
@IntoMap
@ClassKey(OngoingCallController::class)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationActivityStarter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationActivityStarter.kt
index 0f93b5d1ea12..231a0b0b21cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationActivityStarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationActivityStarter.kt
@@ -16,6 +16,11 @@
package com.android.systemui.statusbar.notification
import android.content.Intent
+import android.provider.Settings.ACTION_AUTOMATIC_ZEN_RULE_SETTINGS
+import android.provider.Settings.ACTION_NOTIFICATION_HISTORY
+import android.provider.Settings.ACTION_NOTIFICATION_SETTINGS
+import android.provider.Settings.ACTION_ZEN_MODE_SETTINGS
+import android.provider.Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID
import android.view.View
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
@@ -25,6 +30,7 @@ import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
* (e.g. clicking on a notification, tapping on the settings icon in the notification guts)
*/
interface NotificationActivityStarter {
+
/** Called when the user clicks on the notification bubble icon. */
fun onNotificationBubbleIconClicked(entry: NotificationEntry?)
@@ -35,14 +41,63 @@ interface NotificationActivityStarter {
fun startNotificationGutsIntent(intent: Intent?, appUid: Int, row: ExpandableNotificationRow?)
/**
- * Called when the user clicks "Manage" or "History" in the Shade, or the "No notifications"
- * text.
+ * Called when the user clicks "Manage" or "History" in the Shade. Prefer using
+ * [startSettingsIntent] instead.
*/
fun startHistoryIntent(view: View?, showHistory: Boolean)
+ /**
+ * Called to open a settings intent from a launchable view (such as the "Manage" or "History"
+ * button in the shade, or the "No notifications" text).
+ *
+ * @param view the view to perform the launch animation from (must extend [LaunchableView])
+ * @param intentInfo information about the (settings) intent to be launched
+ */
+ fun startSettingsIntent(view: View, intentInfo: SettingsIntent)
+
/** Called when the user succeed to drop notification to proper target view. */
fun onDragSuccess(entry: NotificationEntry?)
val isCollapsingToShowActivityOverLockscreen: Boolean
get() = false
+
+ /**
+ * Information about a settings intent to be launched.
+ *
+ * If the [targetIntent] is T and [backStack] is [A, B, C], the stack will look like
+ * [A, B, C, T].
+ */
+ data class SettingsIntent(
+ var targetIntent: Intent,
+ var backStack: List<Intent> = emptyList(),
+ var cujType: Int? = null,
+ ) {
+ // Utility factory methods for known intents
+ companion object {
+ fun forNotificationSettings(cujType: Int? = null) =
+ SettingsIntent(
+ targetIntent = Intent(ACTION_NOTIFICATION_SETTINGS),
+ cujType = cujType,
+ )
+
+ fun forNotificationHistory(cujType: Int? = null) =
+ SettingsIntent(
+ targetIntent = Intent(ACTION_NOTIFICATION_HISTORY),
+ backStack = listOf(Intent(ACTION_NOTIFICATION_SETTINGS)),
+ cujType = cujType,
+ )
+
+ fun forModesSettings(cujType: Int? = null) =
+ SettingsIntent(targetIntent = Intent(ACTION_ZEN_MODE_SETTINGS), cujType = cujType)
+
+ fun forModeSettings(modeId: String, cujType: Int? = null) =
+ SettingsIntent(
+ targetIntent =
+ Intent(ACTION_AUTOMATIC_ZEN_RULE_SETTINGS)
+ .putExtra(EXTRA_AUTOMATIC_ZEN_RULE_ID, modeId),
+ backStack = listOf(Intent(ACTION_ZEN_MODE_SETTINGS)),
+ cujType = cujType,
+ )
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt
index b342722ebb09..b67092ca9348 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt
@@ -22,7 +22,7 @@ import com.android.internal.annotations.VisibleForTesting
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.NOTIFICATIONS_USE_PEOPLE_FILTERING
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.notification.collection.NotificationClassificationFlag
-import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype
+import com.android.systemui.statusbar.notification.shared.NotificationMinimalism
import com.android.systemui.statusbar.notification.shared.PriorityPeopleSection
import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING
import com.android.systemui.statusbar.notification.stack.BUCKET_FOREGROUND_SERVICE
@@ -54,7 +54,7 @@ constructor(val proxy: DeviceConfigProxy, val context: Context) {
fun getNotificationBuckets(): IntArray {
if (
PriorityPeopleSection.isEnabled ||
- NotificationMinimalismPrototype.isEnabled ||
+ NotificationMinimalism.isEnabled ||
NotificationClassificationFlag.isEnabled
) {
// We don't need this list to be adaptive, it can be the superset of all features.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinator.kt
index a621b2a02c5d..4e63b920a73d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinator.kt
@@ -35,7 +35,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor
import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
-import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype
+import com.android.systemui.statusbar.notification.shared.NotificationMinimalism
import com.android.systemui.statusbar.notification.stack.BUCKET_TOP_ONGOING
import com.android.systemui.statusbar.notification.stack.BUCKET_TOP_UNSEEN
import com.android.systemui.util.asIndenting
@@ -77,7 +77,7 @@ constructor(
private var unseenFilterEnabled = false
override fun attach(pipeline: NotifPipeline) {
- if (NotificationMinimalismPrototype.isUnexpectedlyInLegacyMode()) {
+ if (NotificationMinimalism.isUnexpectedlyInLegacyMode()) {
return
}
pipeline.addPromoter(unseenNotifPromoter)
@@ -129,26 +129,25 @@ constructor(
}
}
- private fun unseenFeatureEnabled(): Flow<Boolean> {
- // TODO(b/330387368): create LOCK_SCREEN_NOTIFICATION_MINIMALISM setting to use here?
- // Or should we actually just repurpose using the existing setting?
- if (NotificationMinimalismPrototype.isEnabled) {
- return flowOf(true)
+ private fun minimalismFeatureSettingEnabled(): Flow<Boolean> {
+ if (!NotificationMinimalism.isEnabled) {
+ return flowOf(false)
}
- return seenNotificationsInteractor.isLockScreenShowOnlyUnseenNotificationsEnabled()
+ return seenNotificationsInteractor.isLockScreenNotificationMinimalismEnabled()
}
private suspend fun trackUnseenFilterSettingChanges() {
- unseenFeatureEnabled().collectLatest { isSettingEnabled ->
+ // Only filter the seen notifs when the lock screen minimalism feature settings is on.
+ minimalismFeatureSettingEnabled().collectLatest { isMinimalismSettingEnabled ->
// update local field and invalidate if necessary
- if (isSettingEnabled != unseenFilterEnabled) {
- unseenFilterEnabled = isSettingEnabled
+ if (isMinimalismSettingEnabled != unseenFilterEnabled) {
+ unseenFilterEnabled = isMinimalismSettingEnabled
unseenNotifications.clear()
unseenNotifPromoter.invalidateList("unseen setting changed")
}
// if the setting is enabled, then start tracking and filtering unseen notifications
- logger.logTrackingUnseen(isSettingEnabled)
- if (isSettingEnabled) {
+ logger.logTrackingUnseen(isMinimalismSettingEnabled)
+ if (isMinimalismSettingEnabled) {
trackSeenNotifications()
}
}
@@ -178,7 +177,7 @@ constructor(
}
private fun pickOutTopUnseenNotifs(list: List<ListEntry>) {
- if (NotificationMinimalismPrototype.isUnexpectedlyInLegacyMode()) return
+ if (NotificationMinimalism.isUnexpectedlyInLegacyMode()) return
if (!unseenFilterEnabled) return
// Only ever elevate a top unseen notification on keyguard, not even locked shade
if (statusBarStateController.state != StatusBarState.KEYGUARD) {
@@ -215,9 +214,9 @@ constructor(
object : NotifPromoter(TAG) {
override fun shouldPromoteToTopLevel(child: NotificationEntry): Boolean =
when {
- NotificationMinimalismPrototype.isUnexpectedlyInLegacyMode() -> false
+ NotificationMinimalism.isUnexpectedlyInLegacyMode() -> false
seenNotificationsInteractor.isTopOngoingNotification(child) -> true
- !NotificationMinimalismPrototype.ungroupTopUnseen -> false
+ !NotificationMinimalism.ungroupTopUnseen -> false
else -> seenNotificationsInteractor.isTopUnseenNotification(child)
}
}
@@ -225,7 +224,7 @@ constructor(
val topOngoingSectioner =
object : NotifSectioner("TopOngoing", BUCKET_TOP_ONGOING) {
override fun isInSection(entry: ListEntry): Boolean {
- if (NotificationMinimalismPrototype.isUnexpectedlyInLegacyMode()) return false
+ if (NotificationMinimalism.isUnexpectedlyInLegacyMode()) return false
return entry.anyEntry { notificationEntry ->
seenNotificationsInteractor.isTopOngoingNotification(notificationEntry)
}
@@ -235,7 +234,7 @@ constructor(
val topUnseenSectioner =
object : NotifSectioner("TopUnseen", BUCKET_TOP_UNSEEN) {
override fun isInSection(entry: ListEntry): Boolean {
- if (NotificationMinimalismPrototype.isUnexpectedlyInLegacyMode()) return false
+ if (NotificationMinimalism.isUnexpectedlyInLegacyMode()) return false
return entry.anyEntry { notificationEntry ->
seenNotificationsInteractor.isTopUnseenNotification(notificationEntry)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorLogger.kt
index e44a77c30999..a4fa72942380 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinatorLogger.kt
@@ -34,7 +34,10 @@ constructor(
TAG,
LogLevel.DEBUG,
messageInitializer = { bool1 = trackingUnseen },
- messagePrinter = { "${if (bool1) "Start" else "Stop"} tracking unseen notifications." },
+ messagePrinter = {
+ "${if (bool1) "Start" else "Stop"} " +
+ "tracking unseen notifications because of settings change."
+ },
)
fun logShadeVisible(numUnseen: Int) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
index 73ce48b2324a..96c260bb0852 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
@@ -25,7 +25,7 @@ import com.android.systemui.statusbar.notification.collection.SortBySectionTimeF
import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider
-import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype
+import com.android.systemui.statusbar.notification.shared.NotificationMinimalism
import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
import com.android.systemui.statusbar.notification.shared.PriorityPeopleSection
import javax.inject.Inject
@@ -88,11 +88,10 @@ constructor(
mCoordinators.add(hideLocallyDismissedNotifsCoordinator)
mCoordinators.add(hideNotifsForOtherUsersCoordinator)
mCoordinators.add(keyguardCoordinator)
- if (NotificationMinimalismPrototype.isEnabled) {
+ if (NotificationMinimalism.isEnabled) {
mCoordinators.add(lockScreenMinimalismCoordinator)
- } else {
- mCoordinators.add(unseenKeyguardCoordinator)
}
+ mCoordinators.add(unseenKeyguardCoordinator)
mCoordinators.add(rankingCoordinator)
mCoordinators.add(colorizedFgsCoordinator)
mCoordinators.add(deviceProvisionedCoordinator)
@@ -125,11 +124,11 @@ constructor(
}
// Manually add Ordered Sections
- if (NotificationMinimalismPrototype.isEnabled) {
+ if (NotificationMinimalism.isEnabled) {
mOrderedSections.add(lockScreenMinimalismCoordinator.topOngoingSectioner) // Top Ongoing
}
mOrderedSections.add(headsUpCoordinator.sectioner) // HeadsUp
- if (NotificationMinimalismPrototype.isEnabled) {
+ if (NotificationMinimalism.isEnabled) {
mOrderedSections.add(lockScreenMinimalismCoordinator.topUnseenSectioner) // Top Unseen
}
mOrderedSections.add(colorizedFgsCoordinator.sectioner) // ForegroundService
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 bfea2ba6b839..cf1329c6b564 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
@@ -35,7 +35,6 @@ 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.notification.shared.NotificationMinimalismPrototype
import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.statusbar.policy.headsUpEvents
import com.android.systemui.util.asIndenting
@@ -51,7 +50,6 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
@@ -87,7 +85,6 @@ constructor(
private var unseenFilterEnabled = false
override fun attach(pipeline: NotifPipeline) {
- NotificationMinimalismPrototype.assertInLegacyMode()
pipeline.addFinalizeFilter(unseenNotifFilter)
pipeline.addCollectionListener(collectionListener)
scope.launch { trackUnseenFilterSettingChanges() }
@@ -253,10 +250,6 @@ constructor(
}
private fun unseenFeatureEnabled(): Flow<Boolean> {
- if (NotificationMinimalismPrototype.isEnabled) {
- // TODO(b/330387368): should this really just be turned off? If so, hide the setting.
- return flowOf(false)
- }
return seenNotificationsInteractor.isLockScreenShowOnlyUnseenNotificationsEnabled()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinator.kt
index 5ff5d2d9a7e5..1fe32c9a873a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinator.kt
@@ -117,18 +117,12 @@ constructor(
(entry.getSbn().getNotification().flags and
FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY) > 0
) {
+ // If we've received an update from the system and the entry is marked
+ // as lifetime extended, that means system server has received a
+ // cancelation in response to a direct reply, and sent an update to
+ // let system ui know that it should rebuild the notification with
+ // that direct reply.
if (
- mNotificationRemoteInputManager.shouldKeepForRemoteInputHistory(
- entry
- )
- ) {
- val newSbn = mRebuilder.rebuildForRemoteInputReply(entry)
- entry.onRemoteInputInserted()
- mNotifUpdater.onInternalNotificationUpdate(
- newSbn,
- "Extending lifetime of notification with remote input",
- )
- } else if (
mNotificationRemoteInputManager.shouldKeepForSmartReplyHistory(
entry
)
@@ -140,16 +134,11 @@ constructor(
"Extending lifetime of notification with smart reply",
)
} else {
- // The app may have re-cancelled a notification after it had already
- // been lifetime extended.
- // Rebuild the notification with the replies it already had to
- // ensure
- // those replies continue to be displayed.
- val newSbn = mRebuilder.rebuildWithExistingReplies(entry)
+ val newSbn = mRebuilder.rebuildForRemoteInputReply(entry)
+ entry.onRemoteInputInserted()
mNotifUpdater.onInternalNotificationUpdate(
newSbn,
- "Extending lifetime of notification that has already been " +
- "lifetime extended.",
+ "Extending lifetime of notification with remote input",
)
}
} else {
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 6d0148a24cf8..41419f31eb7a 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
@@ -41,7 +41,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager;
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.NotificationMinimalismPrototype;
+import com.android.systemui.statusbar.notification.shared.NotificationMinimalism;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.kotlin.BooleanFlowOperators;
@@ -170,7 +170,7 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable {
if (entry == null) {
return false;
}
- boolean isTopUnseen = NotificationMinimalismPrototype.isEnabled()
+ boolean isTopUnseen = NotificationMinimalism.isEnabled()
&& (mSeenNotificationsInteractor.isTopUnseenNotification(entry)
|| mSeenNotificationsInteractor.isTopOngoingNotification(entry));
if (isTopUnseen || mHeadsUpManager.isHeadsUpEntry(entry.getKey())) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt
index 29564326481f..1babe47559e5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt
@@ -23,7 +23,7 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
-import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype
+import com.android.systemui.statusbar.notification.shared.NotificationMinimalism
import com.android.systemui.util.printSection
import com.android.systemui.util.settings.SecureSettings
import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
@@ -57,25 +57,25 @@ constructor(
/** Set the entry that is identified as the top ongoing notification. */
fun setTopOngoingNotification(entry: NotificationEntry?) {
- if (NotificationMinimalismPrototype.isUnexpectedlyInLegacyMode()) return
+ if (NotificationMinimalism.isUnexpectedlyInLegacyMode()) return
notificationListRepository.topOngoingNotificationKey.value = entry?.key
}
/** Determine if the given notification is the top ongoing notification. */
fun isTopOngoingNotification(entry: NotificationEntry?): Boolean =
- if (NotificationMinimalismPrototype.isUnexpectedlyInLegacyMode()) false
+ if (NotificationMinimalism.isUnexpectedlyInLegacyMode()) false
else
entry != null && notificationListRepository.topOngoingNotificationKey.value == entry.key
/** Set the entry that is identified as the top unseen notification. */
fun setTopUnseenNotification(entry: NotificationEntry?) {
- if (NotificationMinimalismPrototype.isUnexpectedlyInLegacyMode()) return
+ if (NotificationMinimalism.isUnexpectedlyInLegacyMode()) return
notificationListRepository.topUnseenNotificationKey.value = entry?.key
}
/** Determine if the given notification is the top unseen notification. */
fun isTopUnseenNotification(entry: NotificationEntry?): Boolean =
- if (NotificationMinimalismPrototype.isUnexpectedlyInLegacyMode()) false
+ if (NotificationMinimalism.isUnexpectedlyInLegacyMode()) false
else entry != null && notificationListRepository.topUnseenNotificationKey.value == entry.key
fun dump(pw: IndentingPrintWriter) =
@@ -120,4 +120,29 @@ constructor(
// only track the most recent emission, if events are happening faster than they can be
// consumed
.conflate()
+
+ fun isLockScreenNotificationMinimalismEnabled(): Flow<Boolean> =
+ secureSettings
+ // emit whenever the setting has changed
+ .observerFlow(
+ UserHandle.USER_ALL,
+ Settings.Secure.LOCK_SCREEN_NOTIFICATION_MINIMALISM,
+ )
+ // perform a query immediately
+ .onStart { emit(Unit) }
+ // for each change, lookup the new value
+ .map {
+ secureSettings.getIntForUser(
+ name = Settings.Secure.LOCK_SCREEN_NOTIFICATION_MINIMALISM,
+ default = 1,
+ userHandle = UserHandle.USER_CURRENT,
+ ) == 1
+ }
+ // don't emit anything if nothing has changed
+ .distinctUntilChanged()
+ // perform lookups on the bg thread pool
+ .flowOn(bgDispatcher)
+ // only track the most recent emission, if events are happening faster than they can be
+ // consumed
+ .conflate()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewbinder/EmptyShadeViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewbinder/EmptyShadeViewBinder.kt
index 102a11c2314c..7f1b04358546 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewbinder/EmptyShadeViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewbinder/EmptyShadeViewBinder.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.emptyshade.ui.viewbinder
import android.view.View
+import com.android.systemui.statusbar.notification.NotificationActivityStarter
import com.android.systemui.statusbar.notification.emptyshade.ui.view.EmptyShadeView
import com.android.systemui.statusbar.notification.emptyshade.ui.viewmodel.EmptyShadeViewModel
import kotlinx.coroutines.coroutineScope
@@ -26,18 +27,16 @@ object EmptyShadeViewBinder {
suspend fun bind(
view: EmptyShadeView,
viewModel: EmptyShadeViewModel,
- launchNotificationSettings: View.OnClickListener,
- launchNotificationHistory: View.OnClickListener,
+ notificationActivityStarter: NotificationActivityStarter,
) = coroutineScope {
launch { viewModel.text.collect { view.setText(it) } }
launch {
- viewModel.tappingShouldLaunchHistory.collect { shouldLaunchHistory ->
- if (shouldLaunchHistory) {
- view.setOnClickListener(launchNotificationHistory)
- } else {
- view.setOnClickListener(launchNotificationSettings)
+ viewModel.onClick.collect { settingsIntent ->
+ val onClickListener = { view: View ->
+ notificationActivityStarter.startSettingsIntent(view, settingsIntent)
}
+ view.setOnClickListener(onClickListener)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModel.kt
index d5417e7ae8f6..8c8f200f78b7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModel.kt
@@ -22,6 +22,7 @@ import com.android.systemui.dump.DumpManager
import com.android.systemui.modes.shared.ModesUi
import com.android.systemui.res.R
import com.android.systemui.shared.notifications.domain.interactor.NotificationSettingsInteractor
+import com.android.systemui.statusbar.notification.NotificationActivityStarter.SettingsIntent
import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
@@ -34,6 +35,7 @@ import java.util.Locale
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
@@ -80,8 +82,7 @@ constructor(
if (ModesUi.isEnabled) {
zenModeInteractor.modesHidingNotifications.map { modes ->
// Create a string that is either "No notifications" if no modes are filtering
- // them
- // out, or something like "Notifications paused by SomeMode" otherwise.
+ // them out, or something like "Notifications paused by SomeMode" otherwise.
val msgFormat =
MessageFormat(
context.getString(R.string.modes_suppressing_shade_text),
@@ -116,9 +117,26 @@ constructor(
)
}
- val tappingShouldLaunchHistory by lazy {
+ val onClick: Flow<SettingsIntent> by lazy {
ModesEmptyShadeFix.assertInNewMode()
- notificationSettingsInteractor.isNotificationHistoryEnabled
+ combine(
+ zenModeInteractor.modesHidingNotifications,
+ notificationSettingsInteractor.isNotificationHistoryEnabled,
+ ) { modes, isNotificationHistoryEnabled ->
+ if (modes.isNotEmpty()) {
+ if (modes.size == 1) {
+ SettingsIntent.forModeSettings(modes[0].id)
+ } else {
+ SettingsIntent.forModesSettings()
+ }
+ } else {
+ if (isNotificationHistoryEnabled) {
+ SettingsIntent.forNotificationHistory()
+ } else {
+ SettingsIntent.forNotificationSettings()
+ }
+ }
+ }
}
@AssistedFactory
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt
index 920541d101cf..22bec5a43230 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt
@@ -19,6 +19,8 @@ package com.android.systemui.statusbar.notification.footer.ui.viewbinder
import android.view.View
import androidx.lifecycle.lifecycleScope
import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.notification.NotificationActivityStarter
+import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix
import com.android.systemui.statusbar.notification.footer.ui.view.FooterView
import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterViewModel
import com.android.systemui.util.ui.isAnimating
@@ -36,6 +38,7 @@ object FooterViewBinder {
clearAllNotifications: View.OnClickListener,
launchNotificationSettings: View.OnClickListener,
launchNotificationHistory: View.OnClickListener,
+ notificationActivityStarter: NotificationActivityStarter,
): DisposableHandle {
return footer.repeatWhenAttached {
lifecycleScope.launch {
@@ -45,6 +48,7 @@ object FooterViewBinder {
clearAllNotifications,
launchNotificationSettings,
launchNotificationHistory,
+ notificationActivityStarter,
)
}
}
@@ -56,6 +60,7 @@ object FooterViewBinder {
clearAllNotifications: View.OnClickListener,
launchNotificationSettings: View.OnClickListener,
launchNotificationHistory: View.OnClickListener,
+ notificationActivityStarter: NotificationActivityStarter,
) = coroutineScope {
launch { bindClearAllButton(footer, viewModel, clearAllNotifications) }
launch {
@@ -64,6 +69,7 @@ object FooterViewBinder {
viewModel,
launchNotificationSettings,
launchNotificationHistory,
+ notificationActivityStarter,
)
}
launch { bindMessage(footer, viewModel) }
@@ -113,13 +119,23 @@ object FooterViewBinder {
viewModel: FooterViewModel,
launchNotificationSettings: View.OnClickListener,
launchNotificationHistory: View.OnClickListener,
+ notificationActivityStarter: NotificationActivityStarter,
) = coroutineScope {
launch {
- viewModel.manageButtonShouldLaunchHistory.collect { shouldLaunchHistory ->
- if (shouldLaunchHistory) {
- footer.setManageButtonClickListener(launchNotificationHistory)
- } else {
- footer.setManageButtonClickListener(launchNotificationSettings)
+ if (ModesEmptyShadeFix.isEnabled) {
+ viewModel.manageOrHistoryButtonClick.collect { settingsIntent ->
+ val onClickListener = { view: View ->
+ notificationActivityStarter.startSettingsIntent(view, settingsIntent)
+ }
+ footer.setManageButtonClickListener(onClickListener)
+ }
+ } else {
+ viewModel.manageButtonShouldLaunchHistory.collect { shouldLaunchHistory ->
+ if (shouldLaunchHistory) {
+ footer.setManageButtonClickListener(launchNotificationHistory)
+ } else {
+ footer.setManageButtonClickListener(launchNotificationSettings)
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
index 90fb7285e939..a3f4cd225130 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
@@ -16,12 +16,17 @@
package com.android.systemui.statusbar.notification.footer.ui.viewmodel
+import android.content.Intent
+import android.provider.Settings
+import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shared.notifications.domain.interactor.NotificationSettingsInteractor
+import com.android.systemui.statusbar.notification.NotificationActivityStarter.SettingsIntent
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
+import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
import com.android.systemui.statusbar.notification.footer.ui.view.FooterView
import com.android.systemui.util.kotlin.sample
@@ -80,7 +85,7 @@ class FooterViewModel(
combine(
shadeInteractor.isShadeFullyExpanded,
shadeInteractor.isShadeTouchable,
- ::Pair
+ ::Pair,
)
.onStart { emit(Pair(false, false)) }
) { clearAllButtonVisible, (isShadeFullyExpanded, animationsEnabled) ->
@@ -93,8 +98,28 @@ class FooterViewModel(
val manageButtonShouldLaunchHistory =
notificationSettingsInteractor.isNotificationHistoryEnabled
+ // TODO(b/366003631): When inlining the flag, consider adding this to FooterButtonViewModel.
+ val manageOrHistoryButtonClick: Flow<SettingsIntent> by lazy {
+ if (ModesEmptyShadeFix.isUnexpectedlyInLegacyMode()) {
+ flowOf(SettingsIntent(Intent(Settings.ACTION_NOTIFICATION_SETTINGS)))
+ } else {
+ notificationSettingsInteractor.isNotificationHistoryEnabled.map {
+ isNotificationHistoryEnabled ->
+ if (isNotificationHistoryEnabled) {
+ SettingsIntent.forNotificationHistory(
+ cujType = InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON
+ )
+ } else {
+ SettingsIntent.forNotificationSettings(
+ cujType = InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON
+ )
+ }
+ }
+ }
+ }
+
private val manageOrHistoryButtonText: Flow<Int> =
- manageButtonShouldLaunchHistory.map { shouldLaunchHistory ->
+ notificationSettingsInteractor.isNotificationHistoryEnabled.map { shouldLaunchHistory ->
if (shouldLaunchHistory) R.string.manage_notifications_history_text
else R.string.manage_notifications_text
}
@@ -128,7 +153,7 @@ object FooterViewModelModule {
activeNotificationsInteractor.get(),
notificationSettingsInteractor.get(),
seenNotificationsInteractor.get(),
- shadeInteractor.get()
+ shadeInteractor.get(),
)
)
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt
index da29b0fd0dc7..ec5ebc3651ee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt
@@ -43,7 +43,7 @@ interface RichOngoingNotificationContentExtractor {
entry: NotificationEntry,
builder: Notification.Builder,
systemUIContext: Context,
- packageContext: Context
+ packageContext: Context,
): RichOngoingContentModel?
}
@@ -52,7 +52,7 @@ class NoOpRichOngoingNotificationContentExtractor : RichOngoingNotificationConte
entry: NotificationEntry,
builder: Notification.Builder,
systemUIContext: Context,
- packageContext: Context
+ packageContext: Context,
): RichOngoingContentModel? = null
}
@@ -68,7 +68,7 @@ class RichOngoingNotificationContentExtractorImpl @Inject constructor() :
entry: NotificationEntry,
builder: Notification.Builder,
systemUIContext: Context,
- packageContext: Context
+ packageContext: Context,
): RichOngoingContentModel? {
val sbn = entry.sbn
val notification = sbn.notification
@@ -89,7 +89,7 @@ class RichOngoingNotificationContentExtractorImpl @Inject constructor() :
null
}
}
- } else if (builder.style is Notification.EnRouteStyle) {
+ } else if (builder.style is Notification.ProgressStyle) {
parseEnRouteNotification(notification, icon)
} else null
} catch (e: Exception) {
@@ -104,7 +104,7 @@ class RichOngoingNotificationContentExtractorImpl @Inject constructor() :
*/
private fun parseTimerNotification(
notification: Notification,
- icon: IconModel
+ icon: IconModel,
): TimerContentModel {
// sortKey=1 0|↺7|RUNNING|▶16:21:58.523|Σ0:05:00|Δ0:00:03|⏳0:04:57
// sortKey=1 0|↺7|PAUSED|Σ0:05:00|Δ0:04:54|⏳0:00:06
@@ -132,7 +132,7 @@ class RichOngoingNotificationContentExtractorImpl @Inject constructor() :
resumeIntent = notification.findStartIntent(),
addMinuteAction = notification.findAddMinuteAction(),
resetAction = notification.findResetAction(),
- )
+ ),
)
}
"RUNNING" -> {
@@ -149,7 +149,7 @@ class RichOngoingNotificationContentExtractorImpl @Inject constructor() :
pauseIntent = notification.findPauseIntent(),
addMinuteAction = notification.findAddMinuteAction(),
resetAction = notification.findResetAction(),
- )
+ ),
)
}
else -> error("unknown state ($state) in sortKey=$sortKey")
@@ -192,7 +192,7 @@ class RichOngoingNotificationContentExtractorImpl @Inject constructor() :
val localDateTime =
LocalDateTime.of(
LocalDate.now(),
- LocalTime.of(hour.toInt(), minute.toInt(), second.toInt(), millis.toInt() * 1000000)
+ LocalTime.of(hour.toInt(), minute.toInt(), second.toInt(), millis.toInt() * 1000000),
)
val offset = ZoneId.systemDefault().rules.getOffset(localDateTime)
return localDateTime.toInstant(offset).toEpochMilli()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationMinimalismPrototype.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationMinimalism.kt
index 06f3db504aaf..70bb2722c678 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationMinimalismPrototype.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationMinimalism.kt
@@ -17,23 +17,23 @@
package com.android.systemui.statusbar.notification.shared
import android.os.SystemProperties
-import com.android.systemui.Flags
+import com.android.server.notification.Flags
import com.android.systemui.flags.FlagToken
import com.android.systemui.flags.RefactorFlagUtils
/** Helper for reading or using the minimalism prototype flag state. */
@Suppress("NOTHING_TO_INLINE")
-object NotificationMinimalismPrototype {
- const val FLAG_NAME = Flags.FLAG_NOTIFICATION_MINIMALISM_PROTOTYPE
+object NotificationMinimalism {
+ const val FLAG_NAME = Flags.FLAG_NOTIFICATION_MINIMALISM
/** A token used for dependency declaration */
val token: FlagToken
get() = FlagToken(FLAG_NAME, isEnabled)
- /** Is the heads-up cycling animation enabled */
+ /** Is the notification minimalism enabled */
@JvmStatic
inline val isEnabled
- get() = Flags.notificationMinimalismPrototype()
+ get() = Flags.notificationMinimalism()
/**
* The prototype will (by default) use a promoter to ensure that the top unseen notification is
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 d246b04b7957..129d4cee9cdb 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
@@ -224,6 +224,7 @@ public class AmbientState implements Dumpable {
* @param isSwipingUp Whether we are swiping up.
*/
public void setSwipingUp(boolean isSwipingUp) {
+ SceneContainerFlag.assertInLegacyMode();
if (!isSwipingUp && mIsSwipingUp) {
// Just stopped swiping up.
mIsFlingRequiredAfterLockScreenSwipeUp = true;
@@ -242,6 +243,7 @@ public class AmbientState implements Dumpable {
* @param isFlinging Whether we are flinging the shade open or closed.
*/
public void setFlinging(boolean isFlinging) {
+ SceneContainerFlag.assertInLegacyMode();
if (isOnKeyguard() && !isFlinging && mIsFlinging) {
// Just stopped flinging.
mIsFlingRequiredAfterLockScreenSwipeUp = false;
@@ -717,6 +719,7 @@ public class AmbientState implements Dumpable {
* @return Whether we need to do a fling down after swiping up on lockscreen.
*/
public boolean isFlingingAfterSwipeUpOnLockscreen() {
+ SceneContainerFlag.assertInLegacyMode();
return mIsFlinging && mIsFlingRequiredAfterLockScreenSwipeUp;
}
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 0a44a2bc9d93..b466bf02387f 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
@@ -568,6 +568,7 @@ public class NotificationStackScrollLayout
private boolean mHasFilteredOutSeenNotifications;
@Nullable private SplitShadeStateController mSplitShadeStateController = null;
private boolean mIsSmallLandscapeLockscreenEnabled = false;
+ private boolean mSuppressHeightUpdates;
/** Pass splitShadeStateController to view and update split shade */
public void passSplitShadeStateController(SplitShadeStateController splitShadeStateController) {
@@ -1458,9 +1459,13 @@ public class NotificationStackScrollLayout
* 2) Swiping up on lockscreen or flinging down after swipe up
*/
private boolean shouldSkipHeightUpdate() {
- return mAmbientState.isOnKeyguard()
- && (mAmbientState.isSwipingUp()
- || mAmbientState.isFlingingAfterSwipeUpOnLockscreen());
+ if (SceneContainerFlag.isEnabled()) {
+ return mSuppressHeightUpdates;
+ } else {
+ return mAmbientState.isOnKeyguard()
+ && (mAmbientState.isSwipingUp()
+ || mAmbientState.isFlingingAfterSwipeUpOnLockscreen());
+ }
}
/**
@@ -5399,6 +5404,7 @@ public class NotificationStackScrollLayout
}
public void setPanelFlinging(boolean flinging) {
+ SceneContainerFlag.assertInLegacyMode();
mAmbientState.setFlinging(flinging);
if (!flinging) {
// re-calculate the stack height which was frozen while flinging
@@ -5406,6 +5412,12 @@ public class NotificationStackScrollLayout
}
}
+ @Override
+ public void suppressHeightUpdates(boolean suppress) {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
+ mSuppressHeightUpdates = suppress;
+ }
+
public void setHeadsUpGoingAwayAnimationsAllowed(boolean headsUpGoingAwayAnimationsAllowed) {
mHeadsUpGoingAwayAnimationsAllowed = headsUpGoingAwayAnimationsAllowed;
}
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 9c5fecf0338e..7b02d0cebfb3 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
@@ -1439,6 +1439,7 @@ public class NotificationStackScrollLayoutController implements Dumpable {
}
public void setPanelFlinging(boolean flinging) {
+ SceneContainerFlag.assertInLegacyMode();
mView.setPanelFlinging(flinging);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
index 06222fdb2761..3bc549543ef2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
@@ -29,7 +29,7 @@ import com.android.systemui.statusbar.StatusBarState.KEYGUARD
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
-import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype
+import com.android.systemui.statusbar.notification.shared.NotificationMinimalism
import com.android.systemui.statusbar.policy.SplitShadeStateController
import com.android.systemui.util.Compile
import com.android.systemui.util.children
@@ -74,7 +74,7 @@ constructor(
/** Whether we allow keyguard to show less important notifications above the shelf. */
private val limitLockScreenToOneImportant
- get() = NotificationMinimalismPrototype.isEnabled
+ get() = NotificationMinimalism.isEnabled
/** Minimum space between two notifications, see [calculateGapAndDividerHeight]. */
private var dividerHeight by notNull<Float>()
@@ -406,7 +406,7 @@ constructor(
fun updateResources() {
maxKeyguardNotifications =
infiniteIfNegative(resources.getInteger(R.integer.keyguard_max_notification_count))
- maxNotificationsExcludesMedia = NotificationMinimalismPrototype.isEnabled
+ maxNotificationsExcludesMedia = NotificationMinimalism.isEnabled
dividerHeight =
max(1f, resources.getDimensionPixelSize(R.dimen.notification_divider_height).toFloat())
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index e0b0ccd9e840..109f0ae41d76 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -148,22 +148,29 @@ public class StackScrollAlgorithm {
if (isHunGoingToShade) {
// Keep 100% opacity for heads up notification going to shade.
viewState.setAlpha(1f);
- } else if ((!SceneContainerFlag.isEnabled() && ambientState.isOnKeyguard())
- || ambientState.isShowingStackOnLockscreen()) {
+ } else if (!SceneContainerFlag.isEnabled() && ambientState.isOnKeyguard()) {
// Adjust alpha for wakeup to lockscreen.
if (view.isHeadsUpState()) {
- // Pulsing HUN should be visible on AOD and stay visible during
+ // Pulsing HUN should be visible on AOD and stay visible during
// AOD=>lockscreen transition
viewState.setAlpha(1f - ambientState.getHideAmount());
- } else if (SceneContainerFlag.isEnabled()) {
+ } else {
+ // Normal notifications are hidden on AOD and should fade in during
+ // AOD=>lockscreen transition
+ viewState.setAlpha(1f - ambientState.getDozeAmount());
+ }
+ } else if (SceneContainerFlag.isEnabled()
+ && ambientState.isShowingStackOnLockscreen()) {
+ // Adjust alpha for wakeup to lockscreen.
+ if (view.isHeadsUpState()) {
+ // Pulsing HUN should be visible on AOD and stay visible during
+ // AOD=>lockscreen transition
+ viewState.setAlpha(1f - ambientState.getHideAmount());
+ } else {
// Take into account scene container-specific Lockscreen fade-in progress
float fadeAlpha = ambientState.getLockscreenStackFadeInProgress();
float dozeAlpha = 1f - ambientState.getDozeAmount();
viewState.setAlpha(Math.min(dozeAlpha, fadeAlpha));
- } else {
- // Normal notifications are hidden on AOD and should fade in during
- // AOD=>lockscreen transition
- viewState.setAlpha(1f - ambientState.getDozeAmount());
}
} else if (ambientState.isExpansionChanging()) {
// Adjust alpha for shade open & close.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
index 0113e361b3d7..dbe81c10e2fd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
@@ -124,4 +124,7 @@ interface NotificationScrollView {
/** @see addHeadsUpHeightChangedListener */
fun removeHeadsUpHeightChangedListener(runnable: Runnable)
+
+ /** Sets whether updates to the stack are are suppressed. */
+ fun suppressHeightUpdates(suppress: Boolean)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
index 3dad32662893..ebae235f88d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
@@ -187,6 +187,7 @@ constructor(
},
launchNotificationSettings,
launchNotificationHistory,
+ notificationActivityStarter.get(),
)
if (SceneContainerFlag.isEnabled) {
launch {
@@ -266,8 +267,7 @@ constructor(
EmptyShadeViewBinder.bind(
emptyShadeView,
emptyShadeViewModel,
- launchNotificationSettings,
- launchNotificationHistory,
+ notificationActivityStarter.get(),
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
index 99ff678d10dd..87d70ba12012 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
@@ -111,6 +111,7 @@ constructor(
launch {
viewModel.shouldCloseGuts.filter { it }.collect { view.closeGutsOnSceneTouch() }
}
+ launch { viewModel.suppressHeightUpdates.collect { view.suppressHeightUpdates(it) } }
launchAndDispose {
view.setSyntheticScrollConsumer(viewModel.syntheticScrollConsumer)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
index cd9c07e38b3a..c9eaec7c5b85 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
@@ -18,6 +18,7 @@
package com.android.systemui.statusbar.notification.stack.ui.viewmodel
import com.android.compose.animation.scene.ContentKey
+import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.ObservableTransitionState.Idle
import com.android.compose.animation.scene.ObservableTransitionState.Transition
import com.android.compose.animation.scene.ObservableTransitionState.Transition.ChangeScene
@@ -48,6 +49,7 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull
/** ViewModel which represents the state of the NSSL/Controller in the world of flexiglass */
@@ -129,6 +131,14 @@ constructor(
}
}
+ /** Are notification stack height updates suppressed? */
+ val suppressHeightUpdates: Flow<Boolean> =
+ sceneInteractor.transitionState.map { transition: ObservableTransitionState ->
+ transition is Transition &&
+ transition.fromContent == Scenes.Lockscreen &&
+ (transition.toContent == Scenes.Bouncer || transition.toContent == Scenes.Gone)
+ }
+
/**
* The expansion fraction of the notification stack. It should go from 0 to 1 when transitioning
* from Gone to Shade scenes, and remain at 1 when in Lockscreen or Shade scenes and while
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index 57be62932e59..0ad22e0b6dc9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -61,6 +61,7 @@ import com.android.systemui.keyguard.ui.viewmodel.LockscreenToPrimaryBouncerTran
import com.android.systemui.keyguard.ui.viewmodel.OccludedToAodTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.OccludedToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.OffToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.ViewStateAccessor
@@ -132,6 +133,7 @@ constructor(
private val occludedToAodTransitionViewModel: OccludedToAodTransitionViewModel,
private val occludedToGoneTransitionViewModel: OccludedToGoneTransitionViewModel,
private val occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel,
+ private val offToLockscreenTransitionViewModel: OffToLockscreenTransitionViewModel,
private val primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel,
private val primaryBouncerToLockscreenTransitionViewModel:
PrimaryBouncerToLockscreenTransitionViewModel,
@@ -444,6 +446,7 @@ constructor(
occludedToAodTransitionViewModel.lockscreenAlpha,
occludedToGoneTransitionViewModel.notificationAlpha(viewState),
occludedToLockscreenTransitionViewModel.lockscreenAlpha,
+ offToLockscreenTransitionViewModel.lockscreenAlpha,
primaryBouncerToLockscreenTransitionViewModel.lockscreenAlpha(viewState),
glanceableHubToLockscreenTransitionViewModel.keyguardAlpha,
lockscreenToGlanceableHubTransitionViewModel.keyguardAlpha,
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 1d3f0e1f6dc3..5f4f72f293a6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -241,10 +241,10 @@ import com.android.wm.shell.bubbles.Bubbles;
import com.android.wm.shell.startingsurface.SplashscreenContentDrawer;
import com.android.wm.shell.startingsurface.StartingSurface;
-import dagger.Lazy;
-
import dalvik.annotation.optimization.NeverCompile;
+import dagger.Lazy;
+
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Map;
@@ -304,6 +304,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
};
void onStatusBarWindowStateChanged(@WindowVisibleState int state) {
+ StatusBarSimpleFragment.assertInLegacyMode();
mStatusBarWindowState = state;
updateBubblesVisibility();
}
@@ -813,8 +814,9 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
mStartingSurfaceOptional = startingSurfaceOptional;
mDreamManager = dreamManager;
lockscreenShadeTransitionController.setCentralSurfaces(this);
- statusBarWindowStateController.addListener(this::onStatusBarWindowStateChanged);
-
+ if (!StatusBarSimpleFragment.isEnabled()) {
+ statusBarWindowStateController.addListener(this::onStatusBarWindowStateChanged);
+ }
mScreenOffAnimationController = screenOffAnimationController;
ShadeExpansionListener shadeExpansionListener = this::onPanelExpansionChanged;
@@ -901,10 +903,12 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
mWallpaperSupported = mWallpaperManager.isWallpaperSupported();
RegisterStatusBarResult result = null;
- try {
- result = mBarService.registerStatusBar(mCommandQueue);
- } catch (RemoteException ex) {
- ex.rethrowFromSystemServer();
+ if (!StatusBarSimpleFragment.isEnabled()) {
+ try {
+ result = mBarService.registerStatusBar(mCommandQueue);
+ } catch (RemoteException ex) {
+ ex.rethrowFromSystemServer();
+ }
}
createAndAddWindows(result);
@@ -912,30 +916,45 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
// Set up the initial notification state. This needs to happen before CommandQueue.disable()
setUpPresenter();
- if ((result.mTransientBarTypes & WindowInsets.Type.statusBars()) != 0) {
- mStatusBarModeRepository.getDefaultDisplay().showTransient();
- }
- mCommandQueueCallbacks.onSystemBarAttributesChanged(mDisplayId, result.mAppearance,
- result.mAppearanceRegions, result.mNavbarColorManagedByIme, result.mBehavior,
- result.mRequestedVisibleTypes, result.mPackageName, result.mLetterboxDetails);
-
- // StatusBarManagerService has a back up of IME token and it's restored here.
- mCommandQueueCallbacks.setImeWindowStatus(mDisplayId, result.mImeWindowVis,
- result.mImeBackDisposition, result.mShowImeSwitcher);
-
- // Set up the initial icon state
- int numIcons = result.mIcons.size();
- for (int i = 0; i < numIcons; i++) {
- mCommandQueue.setIcon(result.mIcons.keyAt(i), result.mIcons.valueAt(i));
- }
-
- if (DEBUG) {
- Log.d(TAG, String.format(
- "init: icons=%d disabled=0x%08x lights=0x%08x imeButton=0x%08x",
- numIcons,
- result.mDisabledFlags1,
+ // When the StatusBarSimpleFragment flag is enabled, this logic will be done in
+ // StatusBarOrchestrator
+ if (!StatusBarSimpleFragment.isEnabled()) {
+ if ((result.mTransientBarTypes & WindowInsets.Type.statusBars()) != 0) {
+ mStatusBarModeRepository.getDefaultDisplay().showTransient();
+ }
+ mCommandQueueCallbacks.onSystemBarAttributesChanged(
+ mDisplayId,
result.mAppearance,
- result.mImeWindowVis));
+ result.mAppearanceRegions,
+ result.mNavbarColorManagedByIme,
+ result.mBehavior,
+ result.mRequestedVisibleTypes,
+ result.mPackageName,
+ result.mLetterboxDetails);
+
+ // StatusBarManagerService has a back up of IME token and it's restored here.
+ mCommandQueueCallbacks.setImeWindowStatus(
+ mDisplayId,
+ result.mImeWindowVis,
+ result.mImeBackDisposition,
+ result.mShowImeSwitcher);
+
+ // Set up the initial icon state
+ int numIcons = result.mIcons.size();
+ for (int i = 0; i < numIcons; i++) {
+ mCommandQueue.setIcon(result.mIcons.keyAt(i), result.mIcons.valueAt(i));
+ }
+
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ String.format(
+ "init: icons=%d disabled=0x%08x lights=0x%08x imeButton=0x%08x",
+ numIcons,
+ result.mDisabledFlags1,
+ result.mAppearance,
+ result.mImeWindowVis));
+ }
}
IntentFilter internalFilter = new IntentFilter();
@@ -1005,24 +1024,30 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
mAccessibilityFloatingMenuController.init();
- // set the initial view visibility
- int disabledFlags1 = result.mDisabledFlags1;
- int disabledFlags2 = result.mDisabledFlags2;
- mInitController.addPostInitTask(() -> {
- setUpDisableFlags(disabledFlags1, disabledFlags2);
- try {
- // NOTE(b/262059863): Force-update the disable flags after applying the flags
- // returned from registerStatusBar(). The result's disabled flags may be stale
- // if StatusBarManager's disabled flags are updated between registering the bar and
- // this handling this post-init task. We force an update in this case, and use a new
- // token to not conflict with any other disabled flags already requested by SysUI
- Binder token = new Binder();
- mBarService.disable(DISABLE_HOME, token, mContext.getPackageName());
- mBarService.disable(0, token, mContext.getPackageName());
- } catch (RemoteException ex) {
- ex.rethrowFromSystemServer();
- }
- });
+ // When the StatusBarSimpleFragment flag is enabled, this logic will be done in
+ // StatusBarOrchestrator
+ if (!StatusBarSimpleFragment.isEnabled()) {
+ // set the initial view visibility
+ int disabledFlags1 = result.mDisabledFlags1;
+ int disabledFlags2 = result.mDisabledFlags2;
+ mInitController.addPostInitTask(
+ () -> {
+ setUpDisableFlags(disabledFlags1, disabledFlags2);
+ try {
+ // NOTE(b/262059863): Force-update the disable flags after applying the
+ // flags returned from registerStatusBar(). The result's disabled flags
+ // may be stale if StatusBarManager's disabled flags are updated between
+ // registering the bar and this handling this post-init task. We force
+ // an update in this case, and use a new token to not conflict with any
+ // other disabled flags already requested by SysUI
+ Binder token = new Binder();
+ mBarService.disable(DISABLE_HOME, token, mContext.getPackageName());
+ mBarService.disable(0, token, mContext.getPackageName());
+ } catch (RemoteException ex) {
+ ex.rethrowFromSystemServer();
+ }
+ });
+ }
registerCallbacks();
@@ -1101,7 +1126,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
/**
* @deprecated use {@link
- * WindowRootViewVisibilityInteractor.isLockscreenOrShadeVisible} instead.
+ * WindowRootViewVisibilityInteractor#isLockscreenOrShadeVisible()} instead.
*/ @VisibleForTesting
@Deprecated
void initShadeVisibilityListener() {
@@ -1168,13 +1193,16 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
mWallpaperController.setRootView(getNotificationShadeWindowView());
mDemoModeController.addCallback(mDemoModeCallback);
- mJavaAdapter.alwaysCollectFlow(
- mStatusBarModeRepository.getDefaultDisplay().isTransientShown(),
- this::onTransientShownChanged);
- mJavaAdapter.alwaysCollectFlow(
- mStatusBarModeRepository.getDefaultDisplay().getStatusBarMode(),
- this::updateBarMode);
-
+ // When the StatusBarSimpleFragment flag is enabled, this logic will be done in
+ // StatusBarOrchestrator.
+ if (!StatusBarSimpleFragment.isEnabled()) {
+ mJavaAdapter.alwaysCollectFlow(
+ mStatusBarModeRepository.getDefaultDisplay().isTransientShown(),
+ this::onTransientShownChanged);
+ mJavaAdapter.alwaysCollectFlow(
+ mStatusBarModeRepository.getDefaultDisplay().getStatusBarMode(),
+ this::updateBarMode);
+ }
mCommandQueueCallbacks = mCommandQueueCallbacksLazy.get();
mCommandQueue.addCallback(mCommandQueueCallbacks);
@@ -1184,59 +1212,70 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
mShadeExpansionStateManager.addExpansionListener(mWakeUpCoordinator);
mWakeUpCoordinator.onPanelExpansionChanged(currentState);
- // Allow plugins to reference DarkIconDispatcher and StatusBarStateController
- mPluginDependencyProvider.allowPluginDependency(DarkIconDispatcher.class);
- mPluginDependencyProvider.allowPluginDependency(StatusBarStateController.class);
-
- // Set up CollapsedStatusBarFragment and PhoneStatusBarView
- mStatusBarInitializer.setStatusBarViewUpdatedListener(
- (statusBarViewController, statusBarTransitions) -> {
- mPhoneStatusBarViewController = statusBarViewController;
- mStatusBarTransitions = statusBarTransitions;
- getNotificationShadeWindowViewController()
- .setStatusBarViewController(mPhoneStatusBarViewController);
- // Ensure we re-propagate panel expansion values to the panel controller and
- // any listeners it may have, such as PanelBar. This will also ensure we
- // re-display the notification panel if necessary (for example, if
- // a heads-up notification was being displayed and should continue being
- // displayed).
- mShadeSurface.updateExpansionAndVisibility();
- setBouncerShowingForStatusBarComponents(mBouncerShowing);
- checkBarModes();
- });
- // When the flag is on, we register the fragment as a core startable and this is not needed
+ // When the StatusBarSimpleFragment flag is enabled, all this logic will be done in
+ // StatusBarOrchestrator.
if (!StatusBarSimpleFragment.isEnabled()) {
+ // Allow plugins to reference DarkIconDispatcher and StatusBarStateController
+ mPluginDependencyProvider.allowPluginDependency(DarkIconDispatcher.class);
+ mPluginDependencyProvider.allowPluginDependency(StatusBarStateController.class);
+
+ // Set up CollapsedStatusBarFragment and PhoneStatusBarView
+ mStatusBarInitializer.setStatusBarViewUpdatedListener(
+ (statusBarViewController, statusBarTransitions) -> {
+
+ mPhoneStatusBarViewController = statusBarViewController;
+ mStatusBarTransitions = statusBarTransitions;
+ getNotificationShadeWindowViewController()
+ .setStatusBarViewController(mPhoneStatusBarViewController);
+ // Ensure we re-propagate panel expansion values to the panel controller and
+ // any listeners it may have, such as PanelBar. This will also ensure we
+ // re-display the notification panel if necessary (for example, if
+ // a heads-up notification was being displayed and should continue being
+ // displayed).
+ mShadeSurface.updateExpansionAndVisibility();
+ setBouncerShowingForStatusBarComponents(mBouncerShowing);
+ checkBarModes();
+ });
+ // When the flag is on, we register the fragment as a core startable and this is not
+ // needed
mStatusBarInitializer.initializeStatusBar();
}
mStatusBarTouchableRegionManager.setup(getNotificationShadeWindowView());
- createNavigationBar(result);
+ if (!StatusBarSimpleFragment.isEnabled()) {
+ createNavigationBar(result);
+ }
mAmbientIndicationContainer = getNotificationShadeWindowView().findViewById(
R.id.ambient_indication_container);
- mAutoHideController.setStatusBar(new AutoHideUiElement() {
- @Override
- public void synchronizeState() {
- checkBarModes();
- }
+ // When the StatusBarSimpleFragment flag is enabled, all this logic will be done in
+ // StatusBarOrchestrator.
+ if (!StatusBarSimpleFragment.isEnabled()) {
+ mAutoHideController.setStatusBar(
+ new AutoHideUiElement() {
+ @Override
+ public void synchronizeState() {
+ checkBarModes();
+ }
- @Override
- public boolean shouldHideOnTouch() {
- return !mRemoteInputManager.isRemoteInputActive();
- }
+ @Override
+ public boolean shouldHideOnTouch() {
+ return !mRemoteInputManager.isRemoteInputActive();
+ }
- @Override
- public boolean isVisible() {
- return isTransientShown();
- }
+ @Override
+ public boolean isVisible() {
+ return isTransientShown();
+ }
- @Override
- public void hide() {
- mStatusBarModeRepository.getDefaultDisplay().clearTransient();
- }
- });
+ @Override
+ public void hide() {
+ mStatusBarModeRepository.getDefaultDisplay().clearTransient();
+ }
+ });
+ }
ScrimView scrimBehind = getNotificationShadeWindowView().findViewById(R.id.scrim_behind);
ScrimView notificationsScrim = getNotificationShadeWindowView()
@@ -1479,12 +1518,14 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
* @param state2 disable2 flags
*/
protected void setUpDisableFlags(int state1, int state2) {
+ StatusBarSimpleFragment.assertInLegacyMode();
mCommandQueue.disable(mDisplayId, state1, state2, false /* animate */);
}
// TODO(b/117478341): This was left such that CarStatusBar can override this method.
// Try to remove this.
protected void createNavigationBar(@Nullable RegisterStatusBarResult result) {
+ StatusBarSimpleFragment.assertInLegacyMode();
mNavigationBarController.createNavigationBars(true /* includeDefaultDisplay */, result);
}
@@ -1697,14 +1738,16 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
@Override
public void checkBarModes() {
if (mDemoModeController.isInDemoMode()) return;
- if (mStatusBarTransitions != null) {
+ // When the StatusBarSimpleFragment flag is enabled, this logic will be done in
+ // StatusBarOrchestrator.
+ if (!StatusBarSimpleFragment.isEnabled() && mStatusBarTransitions != null) {
checkBarMode(
mStatusBarModeRepository.getDefaultDisplay().getStatusBarMode().getValue(),
mStatusBarWindowState,
mStatusBarTransitions);
+ mNoAnimationOnNextBarModeChange = false;
}
mNavigationBarController.checkNavBarModes(mDisplayId);
- mNoAnimationOnNextBarModeChange = false;
}
/** Temporarily hides Bubbles if the status bar is hidden. */
@@ -1728,7 +1771,9 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
}
private void finishBarAnimations() {
- if (mStatusBarTransitions != null) {
+ // When the StatusBarSimpleFragment flag is enabled, this logic will be done in
+ // StatusBarOrchestrator.
+ if (!StatusBarSimpleFragment.isEnabled() && mStatusBarTransitions != null) {
mStatusBarTransitions.finishAnimations();
}
mNavigationBarController.finishBarAnimations(mDisplayId);
@@ -1770,14 +1815,17 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
}
pw.print(" mInteractingWindows="); pw.println(mInteractingWindows);
- pw.print(" mStatusBarWindowState=");
- pw.println(windowStateToString(mStatusBarWindowState));
+ if (!StatusBarSimpleFragment.isEnabled()) {
+ pw.print(" mStatusBarWindowState=");
+ pw.println(windowStateToString(mStatusBarWindowState));
+ }
pw.print(" mDozing="); pw.println(mDozing);
pw.print(" mWallpaperSupported= "); pw.println(mWallpaperSupported);
- CentralSurfaces.dumpBarTransitions(
- pw, "PhoneStatusBarTransitions", mStatusBarTransitions);
-
+ if (!StatusBarSimpleFragment.isEnabled()) {
+ CentralSurfaces.dumpBarTransitions(
+ pw, "PhoneStatusBarTransitions", mStatusBarTransitions);
+ }
pw.println(" mMediaManager: ");
if (mMediaManager != null) {
mMediaManager.dump(pw, args);
@@ -1850,7 +1898,11 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
private void createAndAddWindows(@Nullable RegisterStatusBarResult result) {
makeStatusBarView(result);
mNotificationShadeWindowController.attach();
- mStatusBarWindowController.attach();
+ // When the StatusBarSimpleFragment flag is enabled, this logic will be done in
+ // StatusBarOrchestrator
+ if (!StatusBarSimpleFragment.isEnabled()) {
+ mStatusBarWindowController.attach();
+ }
}
// called by makeStatusbar and also by PhoneStatusBarView
@@ -2475,7 +2527,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
int importance = bouncerShowing
? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
: IMPORTANT_FOR_ACCESSIBILITY_AUTO;
- if (mPhoneStatusBarViewController != null) {
+ if (!StatusBarSimpleFragment.isEnabled() && mPhoneStatusBarViewController != null) {
mPhoneStatusBarViewController.setImportantForAccessibility(importance);
}
mShadeSurface.setImportantForAccessibility(importance);
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 0a6e7f59e24e..93db2db918b0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -20,6 +20,7 @@ import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
import static android.service.notification.NotificationListenerService.REASON_CLICK;
import static com.android.systemui.statusbar.phone.CentralSurfaces.getActivityOptions;
+import static com.android.systemui.util.kotlin.NullabilityKt.expectNotNull;
import android.app.ActivityManager;
import android.app.ActivityOptions;
@@ -43,6 +44,7 @@ import android.text.TextUtils;
import android.util.EventLog;
import android.view.View;
+import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import com.android.internal.jank.InteractionJankMonitor;
@@ -74,6 +76,7 @@ import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorCon
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.provider.LaunchFullScreenIntentProvider;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
+import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowDragController;
import com.android.systemui.statusbar.notification.row.OnUserInteractionCallback;
@@ -110,6 +113,8 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
boolean showOverTheLockScreen);
}
+ private final static String TAG = "StatusBarNotificationActivityStarter";
+
private final Context mContext;
private final int mDisplayId;
@@ -227,6 +232,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
*/
@Override
public void onNotificationBubbleIconClicked(NotificationEntry entry) {
+ expectNotNull(TAG, "entry", entry);
Runnable action = () -> {
mBubblesManagerOptional.ifPresent(bubblesManager ->
bubblesManager.onUserChangedBubble(entry, !entry.isBubble()));
@@ -249,10 +255,12 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
* Called when a notification is clicked.
*
* @param entry notification that was clicked
- * @param row row for that notification
+ * @param row row for that notification
*/
@Override
public void onNotificationClicked(NotificationEntry entry, ExpandableNotificationRow row) {
+ expectNotNull(TAG, "entry", entry);
+ expectNotNull(TAG, "row", row);
mLogger.logStartingActivityFromClick(entry, row.isHeadsUpState(),
mKeyguardStateController.isVisible(),
mNotificationShadeWindowController.getPanelExpanded());
@@ -435,6 +443,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
*/
@Override
public void onDragSuccess(NotificationEntry entry) {
+ expectNotNull(TAG, "entry", entry);
// this method is not responsible for intent sending.
// will focus follow operation only after drag-and-drop that notification.
final NotificationVisibility nv = mVisibilityProvider.obtain(entry, true);
@@ -527,6 +536,8 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
@Override
public void startNotificationGutsIntent(final Intent intent, final int appUid,
ExpandableNotificationRow row) {
+ expectNotNull(TAG, "intent", intent);
+ expectNotNull(TAG, "row", row);
boolean animate = mActivityStarter.shouldAnimateLaunch(true /* isActivityIntent */);
ActivityStarter.OnDismissAction onDismissAction = new ActivityStarter.OnDismissAction() {
@Override
@@ -547,8 +558,8 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
(adapter) -> TaskStackBuilder.create(mContext)
.addNextIntentWithParentStack(intent)
.startActivities(getActivityOptions(
- mDisplayId,
- adapter),
+ mDisplayId,
+ adapter),
new UserHandle(UserHandle.getUserId(appUid))));
});
return true;
@@ -565,6 +576,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
@Override
public void startHistoryIntent(View view, boolean showHistory) {
+ ModesEmptyShadeFix.assertInLegacyMode();
boolean animate = mActivityStarter.shouldAnimateLaunch(true /* isActivityIntent */);
ActivityStarter.OnDismissAction onDismissAction = new ActivityStarter.OnDismissAction() {
@Override
@@ -585,14 +597,14 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
);
ActivityTransitionAnimator.Controller animationController =
viewController == null ? null
- : new StatusBarTransitionAnimatorController(
- viewController,
- mShadeAnimationInteractor,
- mShadeController,
- mNotificationShadeWindowController,
- mCommandQueue,
- mDisplayId,
- true /* isActivityIntent */);
+ : new StatusBarTransitionAnimatorController(
+ viewController,
+ mShadeAnimationInteractor,
+ mShadeController,
+ mNotificationShadeWindowController,
+ mCommandQueue,
+ mDisplayId,
+ true /* isActivityIntent */);
mActivityTransitionAnimator.startIntentWithAnimation(
animationController, animate, intent.getPackage(),
@@ -612,6 +624,51 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
false /* afterKeyguardGone */);
}
+ @Override
+ public void startSettingsIntent(@NonNull View view, @NonNull SettingsIntent intentInfo) {
+ boolean animate = mActivityStarter.shouldAnimateLaunch(true /* isActivityIntent */);
+ ActivityStarter.OnDismissAction onDismissAction = new ActivityStarter.OnDismissAction() {
+ @Override
+ public boolean onDismiss() {
+ AsyncTask.execute(() -> {
+ TaskStackBuilder tsb = TaskStackBuilder.create(mContext);
+ for (Intent intent : intentInfo.getBackStack()) {
+ tsb.addNextIntent(intent);
+ }
+ tsb.addNextIntent(intentInfo.getTargetIntent());
+
+ ActivityTransitionAnimator.Controller viewController =
+ ActivityTransitionAnimator.Controller.fromView(view,
+ intentInfo.getCujType());
+ ActivityTransitionAnimator.Controller animationController =
+ viewController == null ? null
+ : new StatusBarTransitionAnimatorController(
+ viewController,
+ mShadeAnimationInteractor,
+ mShadeController,
+ mNotificationShadeWindowController,
+ mCommandQueue,
+ mDisplayId,
+ true /* isActivityIntent */);
+
+ mActivityTransitionAnimator.startIntentWithAnimation(
+ animationController, animate, intentInfo.getTargetIntent().getPackage(),
+ (adapter) -> tsb.startActivities(
+ getActivityOptions(mDisplayId, adapter),
+ mUserTracker.getUserHandle()));
+ });
+ return true;
+ }
+
+ @Override
+ public boolean willRunAnimationOnKeyguard() {
+ return animate;
+ }
+ };
+ mActivityStarter.dismissKeyguardThenExecute(onDismissAction, null,
+ false /* afterKeyguardGone */);
+ }
+
private void removeHunAfterClick(ExpandableNotificationRow row) {
String key = row.getEntry().getSbn().getKey();
if (mHeadsUpManager != null && mHeadsUpManager.isHeadsUpEntry(key)) {
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 13b651e8c0be..5b0319883b5f 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
@@ -16,10 +16,20 @@
package com.android.systemui.statusbar.phone.dagger
import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.core.CommandQueueInitializer
import com.android.systemui.statusbar.core.StatusBarInitializer
import com.android.systemui.statusbar.core.StatusBarInitializerImpl
+import com.android.systemui.statusbar.core.StatusBarOrchestrator
+import com.android.systemui.statusbar.core.StatusBarSimpleFragment
+import com.android.systemui.statusbar.phone.CentralSurfacesCommandQueueCallbacks
+import com.android.systemui.statusbar.window.data.repository.StatusBarWindowStateRepositoryStore
+import com.android.systemui.statusbar.window.data.repository.StatusBarWindowStateRepositoryStoreImpl
import dagger.Binds
+import dagger.Lazy
import dagger.Module
+import dagger.Provides
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
@@ -27,6 +37,16 @@ import dagger.multibindings.IntoMap
@Module
interface StatusBarPhoneModule {
+ @Binds
+ abstract fun windowStateRepoStore(
+ impl: StatusBarWindowStateRepositoryStoreImpl
+ ): StatusBarWindowStateRepositoryStore
+
+ @Binds
+ abstract fun commandQCallbacks(
+ impl: CentralSurfacesCommandQueueCallbacks
+ ): CommandQueue.Callbacks
+
/** Binds {@link StatusBarInitializer} as a {@link CoreStartable}. */
@Binds
@IntoMap
@@ -34,4 +54,34 @@ interface StatusBarPhoneModule {
fun bindStatusBarInitializer(impl: StatusBarInitializerImpl): CoreStartable
@Binds fun statusBarInitializer(impl: StatusBarInitializerImpl): StatusBarInitializer
+
+ companion object {
+ @Provides
+ @SysUISingleton
+ @IntoMap
+ @ClassKey(StatusBarOrchestrator::class)
+ fun orchestratorCoreStartable(
+ orchestratorLazy: Lazy<StatusBarOrchestrator>
+ ): CoreStartable {
+ return if (StatusBarSimpleFragment.isEnabled) {
+ orchestratorLazy.get()
+ } else {
+ CoreStartable.NOP
+ }
+ }
+
+ @Provides
+ @SysUISingleton
+ @IntoMap
+ @ClassKey(CommandQueueInitializer::class)
+ fun commandQueueInitializerCoreStartable(
+ initializerLazy: Lazy<CommandQueueInitializer>
+ ): CoreStartable {
+ return if (StatusBarSimpleFragment.isEnabled) {
+ initializerLazy.get()
+ } else {
+ CoreStartable.NOP
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/InternetTileIconModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/InternetTileIconModel.kt
new file mode 100644
index 000000000000..f8958e0d002f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/model/InternetTileIconModel.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.pipeline.shared.ui.model
+
+import com.android.systemui.common.shared.model.Icon
+
+sealed interface InternetTileIconModel {
+ data class ResourceId(val resId: Int) : InternetTileIconModel
+
+ data class Cellular(val level: Int) : InternetTileIconModel
+
+ data class Satellite(val resourceIcon: Icon.Resource) : InternetTileIconModel
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt
index e1dcc524c486..a1d5cbea62f9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt
@@ -19,17 +19,21 @@ package com.android.systemui.statusbar.policy.ui.dialog
import android.content.Intent
import android.provider.Settings
import android.util.Log
+import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.paneTitle
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTagsAsResourceId
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import com.android.compose.PlatformButton
import com.android.compose.PlatformOutlinedButton
+import com.android.compose.theme.PlatformTheme
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.animation.DialogCuj
@@ -74,7 +78,7 @@ constructor(
currentDialog?.dismiss()
}
- currentDialog = sysuiDialogFactory.create() { ModesDialogContent(it) }
+ currentDialog = sysuiDialogFactory.create { ModesDialogContent(it) }
currentDialog
?.lifecycle
?.addObserver(
@@ -91,28 +95,40 @@ constructor(
@Composable
private fun ModesDialogContent(dialog: SystemUIDialog) {
- AlertDialogContent(
- modifier = Modifier.semantics {
- testTagsAsResourceId = true
- },
- title = {
- Text(
- modifier = Modifier.testTag("modes_title"),
- text = stringResource(R.string.zen_modes_dialog_title)
- )
- },
- content = { ModeTileGrid(viewModel.get()) },
- neutralButton = {
- PlatformOutlinedButton(onClick = { openSettings(dialog) }) {
- Text(stringResource(R.string.zen_modes_dialog_settings))
- }
- },
- positiveButton = {
- PlatformButton(onClick = { dialog.dismiss() }) {
- Text(stringResource(R.string.zen_modes_dialog_done))
- }
- },
- )
+ // TODO(b/369376884): The composable does correctly update when the theme changes
+ // while the dialog is open, but the background (which we don't control here)
+ // doesn't, which causes us to show things like white text on a white background.
+ // as a workaround, we remember the original theme and keep it on recomposition.
+ val isCurrentlyInDarkTheme = isSystemInDarkTheme()
+ val cachedDarkTheme = remember { isCurrentlyInDarkTheme }
+ PlatformTheme(isDarkTheme = cachedDarkTheme) {
+ AlertDialogContent(
+ modifier =
+ Modifier.semantics {
+ testTagsAsResourceId = true
+ paneTitle = dialog.context.getString(
+ R.string.accessibility_desc_quick_settings
+ )
+ },
+ title = {
+ Text(
+ modifier = Modifier.testTag("modes_title"),
+ text = stringResource(R.string.zen_modes_dialog_title),
+ )
+ },
+ content = { ModeTileGrid(viewModel.get()) },
+ neutralButton = {
+ PlatformOutlinedButton(onClick = { openSettings(dialog) }) {
+ Text(stringResource(R.string.zen_modes_dialog_settings))
+ }
+ },
+ positiveButton = {
+ PlatformButton(onClick = { dialog.dismiss() }) {
+ Text(stringResource(R.string.zen_modes_dialog_done))
+ }
+ },
+ )
+ }
}
@VisibleForTesting
@@ -128,8 +144,8 @@ constructor(
}
activityStarter.startActivity(
ZEN_MODE_SETTINGS_INTENT,
- true /* dismissShade */,
- animationController
+ /* dismissShade= */ true,
+ animationController,
)
}
@@ -163,7 +179,7 @@ constructor(
Log.w(
TAG,
"Cannot launch from dialog, the dialog is not present. " +
- "Will launch activity without animating."
+ "Will launch activity without animating.",
)
}
@@ -172,11 +188,7 @@ constructor(
if (animationController == null) {
currentDialog?.dismiss()
}
- activityStarter.startActivity(
- intent,
- true, /* dismissShade */
- animationController,
- )
+ activityStarter.startActivity(intent, /* dismissShade= */ true, animationController)
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt
index d03b2e717398..e1f7bd59005c 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/view/TouchpadTutorialActivity.kt
@@ -29,6 +29,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.theme.PlatformTheme
import com.android.systemui.inputdevice.tutorial.InputDeviceTutorialLogger
import com.android.systemui.inputdevice.tutorial.InputDeviceTutorialLogger.TutorialContext
+import com.android.systemui.inputdevice.tutorial.KeyboardTouchpadTutorialMetricsLogger
import com.android.systemui.touchpad.tutorial.ui.composable.BackGestureTutorialScreen
import com.android.systemui.touchpad.tutorial.ui.composable.HomeGestureTutorialScreen
import com.android.systemui.touchpad.tutorial.ui.composable.RecentAppsGestureTutorialScreen
@@ -45,6 +46,7 @@ class TouchpadTutorialActivity
constructor(
private val viewModelFactory: TouchpadTutorialViewModel.Factory,
private val logger: InputDeviceTutorialLogger,
+ private val metricsLogger: KeyboardTouchpadTutorialMetricsLogger,
) : ComponentActivity() {
private val vm by viewModels<TouchpadTutorialViewModel>(factoryProducer = { viewModelFactory })
@@ -57,6 +59,7 @@ constructor(
}
// required to handle 3+ fingers on touchpad
window.addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY)
+ metricsLogger.logPeripheralTutorialLaunchedFromSettings()
logger.logOpenTutorial(TutorialContext.TOUCHPAD_TUTORIAL)
}
@@ -85,7 +88,7 @@ fun TouchpadTutorialScreen(vm: TouchpadTutorialViewModel, closeTutorial: () -> U
onBackTutorialClicked = { vm.goTo(BACK_GESTURE) },
onHomeTutorialClicked = { vm.goTo(HOME_GESTURE) },
onRecentAppsTutorialClicked = { vm.goTo(RECENT_APPS_GESTURE) },
- onDoneButtonClicked = closeTutorial
+ onDoneButtonClicked = closeTutorial,
)
BACK_GESTURE ->
BackGestureTutorialScreen(
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
index 64e056da97d4..7c055c8876ae 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
@@ -39,11 +39,7 @@ import kotlinx.coroutines.launch
/** A class allowing Java classes to collect on Kotlin flows. */
@SysUISingleton
-class JavaAdapter
-@Inject
-constructor(
- @Application private val scope: CoroutineScope,
-) {
+class JavaAdapter @Inject constructor(@Application private val scope: CoroutineScope) {
/**
* Collect information for the given [flow], calling [consumer] for each emitted event.
*
@@ -55,10 +51,7 @@ constructor(
* Do *not* call this method in a class's constructor. Instead, call it in
* [com.android.systemui.CoreStartable.start] or similar method.
*/
- fun <T> alwaysCollectFlow(
- flow: Flow<T>,
- consumer: Consumer<T>,
- ): Job {
+ fun <T> alwaysCollectFlow(flow: Flow<T>, consumer: Consumer<T>): Job {
return scope.launch { flow.collect { consumer.accept(it) } }
}
@@ -66,7 +59,7 @@ constructor(
fun <T> stateInApp(
flow: Flow<T>,
initialValue: T,
- started: SharingStarted = SharingStarted.Eagerly
+ started: SharingStarted = SharingStarted.Eagerly,
): StateFlow<T> {
return flow.stateIn(scope, started, initialValue)
}
@@ -117,7 +110,7 @@ fun <A, B, C, R> combineFlows(
flow1: Flow<A>,
flow2: Flow<B>,
flow3: Flow<C>,
- trifunction: (A, B, C) -> R
+ trifunction: (A, B, C) -> R,
): Flow<R> {
return combine(flow1, flow2, flow3, trifunction)
}
@@ -127,7 +120,18 @@ fun <T1, T2, T3, T4, R> combineFlows(
flow2: Flow<T2>,
flow3: Flow<T3>,
flow4: Flow<T4>,
- transform: (T1, T2, T3, T4) -> R
+ transform: (T1, T2, T3, T4) -> R,
): Flow<R> {
return combine(flow, flow2, flow3, flow4, transform)
}
+
+fun <T1, T2, T3, T4, T5, R> combineFlows(
+ flow: Flow<T1>,
+ flow2: Flow<T2>,
+ flow3: Flow<T3>,
+ flow4: Flow<T4>,
+ flow5: Flow<T5>,
+ transform: (T1, T2, T3, T4, T5) -> R,
+): Flow<R> {
+ return combine(flow, flow2, flow3, flow4, flow5, transform)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/nullability.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/Nullability.kt
index 298dacde8128..1c760bedff58 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/nullability.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/Nullability.kt
@@ -16,6 +16,7 @@
package com.android.systemui.util.kotlin
+import android.util.Log
import java.util.Optional
/**
@@ -28,3 +29,14 @@ inline fun <T : Any, R> transform(value: T?, block: (T) -> R): R? = value?.let(b
*/
@Suppress("NOTHING_TO_INLINE")
inline fun <T> Optional<T>.getOrNull(): T? = orElse(null)
+
+/**
+ * Utility method to check if a value that is technically nullable is actually null. If it is null,
+ * this will crash development builds (but just log on production/droidfood builds). It can be used
+ * as a first step to verify if a nullable value can be made non-nullable instead.
+ */
+fun <T> expectNotNull(logTag: String, name: String, nullable: T?) {
+ if (nullable == null) {
+ Log.wtf(logTag, "Expected value of $name to not be null.")
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualLocationsService.kt b/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualLocationsService.kt
index 3c50c7b4a212..09b1f45f179b 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualLocationsService.kt
+++ b/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualLocationsService.kt
@@ -1,6 +1,7 @@
package com.android.systemui.wallet.controller
import android.content.Intent
+import android.os.DeadObjectException
import android.os.IBinder
import android.util.Log
import androidx.annotation.VisibleForTesting
@@ -47,7 +48,11 @@ constructor(
controller.allWalletCards.collect { cards ->
val cardsSize = cards.size
Log.i(TAG, "Number of cards registered $cardsSize")
- listener?.registerNewWalletCards(cards)
+ try {
+ listener?.registerNewWalletCards(cards)
+ } catch (e: DeadObjectException) {
+ Log.e(TAG, "Failed to register wallet cards because IWalletCardsUpdatedListener is dead")
+ }
}
}
} else {
@@ -55,7 +60,11 @@ constructor(
controller.allWalletCards.collect { cards ->
val cardsSize = cards.size
Log.i(TAG, "Number of cards registered $cardsSize")
- listener?.registerNewWalletCards(cards)
+ try {
+ listener?.registerNewWalletCards(cards)
+ } catch (e: DeadObjectException) {
+ Log.e(TAG, "Failed to register wallet cards because IWalletCardsUpdatedListener is dead")
+ }
}
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
index 1ceac78af1a2..1ceac78af1a2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
index 2d5e3a6788cc..dddaabb66022 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
@@ -23,10 +23,10 @@ import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.graphics.PointF;
-import android.testing.AndroidTestingRunner;
+
import android.testing.TestableLooper;
import android.view.View;
import android.view.ViewPropertyAnimator;
@@ -37,6 +37,7 @@ import androidx.dynamicanimation.animation.DynamicAnimation;
import androidx.dynamicanimation.animation.FlingAnimation;
import androidx.dynamicanimation.animation.SpringAnimation;
import androidx.dynamicanimation.animation.SpringForce;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.systemui.Prefs;
@@ -57,7 +58,7 @@ import org.mockito.junit.MockitoRule;
import java.util.Optional;
/** Tests for {@link MenuAnimationController}. */
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@SmallTest
public class MenuAnimationControllerTest extends SysuiTestCase {
@@ -172,7 +173,7 @@ public class MenuAnimationControllerTest extends SysuiTestCase {
mMenuAnimationController.mPositionAnimations.values().stream().findAny();
anyAnimation.ifPresent(this::skipAnimationToEnd);
- verifyZeroInteractions(onSpringAnimationsEndCallback);
+ verifyNoMoreInteractions(onSpringAnimationsEndCallback);
}
@Test
@@ -224,7 +225,7 @@ public class MenuAnimationControllerTest extends SysuiTestCase {
.filter(animation -> animation instanceof SpringAnimation)
.forEach(this::skipAnimationToEnd);
- verifyZeroInteractions(onSpringAnimationsEndCallback);
+ verifyNoMoreInteractions(onSpringAnimationsEndCallback);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java
index aa8c6b7a8a5f..e160ff17a6ed 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java
@@ -28,6 +28,7 @@ import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.content.res.Configuration;
@@ -643,6 +644,46 @@ public class TouchMonitorTest extends SysuiTestCase {
environment.verifyInputSessionDispose();
}
+ @Test
+ public void testSessionPopAfterDestroy() {
+ final TouchHandler touchHandler = createTouchHandler();
+
+ final Environment environment = new Environment(Stream.of(touchHandler)
+ .collect(Collectors.toCollection(HashSet::new)), mKosmos);
+
+ final InputEvent initialEvent = Mockito.mock(InputEvent.class);
+ environment.publishInputEvent(initialEvent);
+
+ // Ensure session started
+ final InputChannelCompat.InputEventListener eventListener =
+ registerInputEventListener(touchHandler);
+
+ // First event will be missed since we register after the execution loop,
+ final InputEvent event = Mockito.mock(InputEvent.class);
+ environment.publishInputEvent(event);
+ verify(eventListener).onInputEvent(eq(event));
+
+ final ArgumentCaptor<TouchHandler.TouchSession> touchSessionArgumentCaptor =
+ ArgumentCaptor.forClass(TouchHandler.TouchSession.class);
+
+ verify(touchHandler).onSessionStart(touchSessionArgumentCaptor.capture());
+
+ environment.updateLifecycle(Lifecycle.State.DESTROYED);
+
+ // Check to make sure the input session is now disposed.
+ environment.verifyInputSessionDispose();
+
+ clearInvocations(environment.mInputFactory);
+
+ // Pop the session
+ touchSessionArgumentCaptor.getValue().pop();
+
+ environment.executeAll();
+
+ // Ensure no input sessions were created due to the session reset.
+ verifyNoMoreInteractions(environment.mInputFactory);
+ }
+
@Test
public void testPilfering() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
index 9fbe09619ff1..9fbe09619ff1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerPredictiveBackTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerPredictiveBackTest.kt
index 5cc64547aa6b..0d369a3ea80c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerPredictiveBackTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerPredictiveBackTest.kt
@@ -19,6 +19,7 @@ package com.android.systemui.bouncer.ui.composable
import android.app.AlertDialog
import android.platform.test.annotations.MotionTest
import android.testing.TestableLooper.RunWithLooper
+import android.view.View
import androidx.activity.BackEventCompat
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.tween
@@ -52,27 +53,21 @@ import com.android.systemui.bouncer.ui.BouncerDialogFactory
import com.android.systemui.bouncer.ui.viewmodel.BouncerSceneContentViewModel
import com.android.systemui.bouncer.ui.viewmodel.BouncerUserActionsViewModel
import com.android.systemui.bouncer.ui.viewmodel.bouncerSceneContentViewModel
-import com.android.systemui.classifier.domain.interactor.falsingInteractor
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.motion.createSysUiComposeMotionTestRule
-import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.domain.startable.sceneContainerStartable
-import com.android.systemui.scene.sceneContainerGestureFilterFactory
-import com.android.systemui.scene.shared.logger.sceneLogger
+import com.android.systemui.scene.sceneContainerViewModelFactory
import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.sceneDataSourceDelegator
import com.android.systemui.scene.ui.composable.Scene
import com.android.systemui.scene.ui.composable.SceneContainer
-import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
-import com.android.systemui.scene.ui.viewmodel.splitEdgeDetector
import com.android.systemui.settings.displayTracker
-import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.testKosmos
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.awaitCancellation
@@ -85,6 +80,7 @@ import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.mock
import platform.test.motion.compose.ComposeFeatureCaptures.positionInRoot
import platform.test.motion.compose.ComposeRecordingSpec
import platform.test.motion.compose.MotionControl
@@ -121,24 +117,17 @@ class BouncerPredictiveBackTest : SysuiTestCase() {
val navigationDistances = mapOf(Scenes.Lockscreen to 1, Scenes.Bouncer to 0)
SceneContainerConfig(sceneKeys, initialSceneKey, emptyList(), navigationDistances)
}
+ private val view = mock<View>()
private val transitionState by lazy {
MutableStateFlow<ObservableTransitionState>(
ObservableTransitionState.Idle(kosmos.sceneContainerConfig.initialSceneKey)
)
}
+
private val sceneContainerViewModel by lazy {
- SceneContainerViewModel(
- sceneInteractor = kosmos.sceneInteractor,
- falsingInteractor = kosmos.falsingInteractor,
- powerInteractor = kosmos.powerInteractor,
- shadeInteractor = kosmos.shadeInteractor,
- splitEdgeDetector = kosmos.splitEdgeDetector,
- logger = kosmos.sceneLogger,
- gestureFilterFactory = kosmos.sceneContainerGestureFilterFactory,
- displayId = kosmos.displayTracker.defaultDisplayId,
- motionEventHandlerReceiver = {},
- )
+ kosmos.sceneContainerViewModelFactory
+ .create(view, kosmos.displayTracker.defaultDisplayId, {})
.apply { setTransitionState(transitionState) }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/FalsingDataProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java
index 5a4799cecae5..5a4799cecae5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/classifier/FalsingDataProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
index d72b72c3d21e..ccdaa45513bd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
@@ -24,7 +24,7 @@ import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.app.KeyguardManager;
@@ -121,7 +121,7 @@ public class ClipboardListenerTest extends SysuiTestCase {
public void test_initialization() {
mClipboardListener.start();
verify(mClipboardManager).addPrimaryClipChangedListener(any());
- verifyZeroInteractions(mUiEventLogger);
+ verifyNoMoreInteractions(mUiEventLogger);
}
@Test
@@ -204,7 +204,7 @@ public class ClipboardListenerTest extends SysuiTestCase {
verify(mUiEventLogger, times(1)).log(
ClipboardOverlayEvent.CLIPBOARD_TOAST_SHOWN, 0, mSampleSource);
verify(mClipboardToast, times(1)).showCopiedToast();
- verifyZeroInteractions(mOverlayControllerProvider);
+ verifyNoMoreInteractions(mOverlayControllerProvider);
}
@Test
@@ -218,7 +218,7 @@ public class ClipboardListenerTest extends SysuiTestCase {
verify(mUiEventLogger, times(1)).log(
ClipboardOverlayEvent.CLIPBOARD_TOAST_SHOWN, 0, mSampleSource);
verify(mClipboardToast, times(1)).showCopiedToast();
- verifyZeroInteractions(mOverlayControllerProvider);
+ verifyNoMoreInteractions(mOverlayControllerProvider);
}
@Test
@@ -232,7 +232,7 @@ public class ClipboardListenerTest extends SysuiTestCase {
verify(mUiEventLogger, times(1)).log(
ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ENTERED, 0, mSampleSource);
verify(mOverlayController).setClipData(mSampleClipData, mSampleSource);
- verifyZeroInteractions(mClipboardToast);
+ verifyNoMoreInteractions(mClipboardToast);
}
@Test
@@ -242,9 +242,9 @@ public class ClipboardListenerTest extends SysuiTestCase {
mClipboardListener.start();
mClipboardListener.onPrimaryClipChanged();
- verifyZeroInteractions(mUiEventLogger);
- verifyZeroInteractions(mClipboardToast);
- verifyZeroInteractions(mOverlayControllerProvider);
+ verifyNoMoreInteractions(mUiEventLogger);
+ verifyNoMoreInteractions(mClipboardToast);
+ verifyNoMoreInteractions(mOverlayControllerProvider);
}
@Test
@@ -259,6 +259,6 @@ public class ClipboardListenerTest extends SysuiTestCase {
verify(mUiEventLogger, times(1)).log(
ClipboardOverlayEvent.CLIPBOARD_TOAST_SHOWN, 0, mSampleSource);
verify(mClipboardToast, times(1)).showCopiedToast();
- verifyZeroInteractions(mOverlayControllerProvider);
+ verifyNoMoreInteractions(mOverlayControllerProvider);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index b0810a9edf6b..6608542980b0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -100,6 +100,7 @@ import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.flags.SystemPropertiesHelper;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionBootInteractor;
import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.navigationbar.NavigationModeController;
@@ -199,6 +200,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
private @Mock ShadeWindowLogger mShadeWindowLogger;
private @Mock SelectedUserInteractor mSelectedUserInteractor;
private @Mock KeyguardInteractor mKeyguardInteractor;
+ private @Mock KeyguardTransitionBootInteractor mKeyguardTransitionBootInteractor;
private @Captor ArgumentCaptor<KeyguardStateController.Callback>
mKeyguardStateControllerCallback;
private @Captor ArgumentCaptor<KeyguardUpdateMonitorCallback>
@@ -1294,6 +1296,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
() -> mock(WindowManagerLockscreenVisibilityManager.class),
mSelectedUserInteractor,
mKeyguardInteractor,
+ mKeyguardTransitionBootInteractor,
mock(WindowManagerOcclusionManager.class));
mViewMediator.start();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractorTest.kt
index 8a5af09f52ed..ad5eeabf83d2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractorTest.kt
@@ -65,7 +65,7 @@ class LockscreenSceneTransitionInteractorTest : SysuiTestCase() {
flowOf(Scenes.Lockscreen),
progress,
false,
- flowOf(false)
+ flowOf(false),
)
private val goneToLs =
@@ -75,7 +75,7 @@ class LockscreenSceneTransitionInteractorTest : SysuiTestCase() {
flowOf(Scenes.Lockscreen),
progress,
false,
- flowOf(false)
+ flowOf(false),
)
@Before
@@ -84,7 +84,8 @@ class LockscreenSceneTransitionInteractorTest : SysuiTestCase() {
kosmos.sceneContainerRepository.setTransitionState(sceneTransitions)
testScope.launch {
kosmos.realKeyguardTransitionRepository.emitInitialStepsFromOff(
- KeyguardState.LOCKSCREEN
+ KeyguardState.LOCKSCREEN,
+ testSetup = true,
)
}
}
@@ -105,11 +106,7 @@ class LockscreenSceneTransitionInteractorTest : SysuiTestCase() {
)
progress.value = 0.4f
- assertTransition(
- step = currentStep!!,
- state = TransitionState.RUNNING,
- progress = 0.4f,
- )
+ assertTransition(step = currentStep!!, state = TransitionState.RUNNING, progress = 0.4f)
sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Gone)
assertTransition(
@@ -142,11 +139,7 @@ class LockscreenSceneTransitionInteractorTest : SysuiTestCase() {
)
progress.value = 0.4f
- assertTransition(
- step = currentStep!!,
- state = TransitionState.RUNNING,
- progress = 0.4f,
- )
+ assertTransition(step = currentStep!!, state = TransitionState.RUNNING, progress = 0.4f)
sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Lockscreen)
@@ -191,7 +184,7 @@ class LockscreenSceneTransitionInteractorTest : SysuiTestCase() {
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.AOD,
animator = null,
- modeOnCanceled = TransitionModeOnCanceled.RESET
+ modeOnCanceled = TransitionModeOnCanceled.RESET,
)
)
sceneTransitions.value = lsToGone
@@ -205,11 +198,7 @@ class LockscreenSceneTransitionInteractorTest : SysuiTestCase() {
)
progress.value = 0.4f
- assertTransition(
- step = currentStep!!,
- state = TransitionState.RUNNING,
- progress = 0.4f,
- )
+ assertTransition(step = currentStep!!, state = TransitionState.RUNNING, progress = 0.4f)
sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Lockscreen)
@@ -257,11 +246,7 @@ class LockscreenSceneTransitionInteractorTest : SysuiTestCase() {
)
progress.value = 0.4f
- assertTransition(
- step = currentStep!!,
- state = TransitionState.RUNNING,
- progress = 0.4f,
- )
+ assertTransition(step = currentStep!!, state = TransitionState.RUNNING, progress = 0.4f)
sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Lockscreen)
@@ -297,7 +282,7 @@ class LockscreenSceneTransitionInteractorTest : SysuiTestCase() {
flowOf(Scenes.Lockscreen),
progress,
false,
- flowOf(false)
+ flowOf(false),
)
assertTransition(
@@ -330,11 +315,7 @@ class LockscreenSceneTransitionInteractorTest : SysuiTestCase() {
)
progress.value = 0.4f
- assertTransition(
- step = currentStep!!,
- state = TransitionState.RUNNING,
- progress = 0.4f,
- )
+ assertTransition(step = currentStep!!, state = TransitionState.RUNNING, progress = 0.4f)
sceneTransitions.value = ObservableTransitionState.Idle(Scenes.Gone)
val stepM3 = allSteps[allSteps.size - 3]
@@ -393,7 +374,7 @@ class LockscreenSceneTransitionInteractorTest : SysuiTestCase() {
flowOf(Scenes.Lockscreen),
progress,
false,
- flowOf(false)
+ flowOf(false),
)
assertTransition(
@@ -466,7 +447,7 @@ class LockscreenSceneTransitionInteractorTest : SysuiTestCase() {
flowOf(Scenes.Lockscreen),
progress,
false,
- flowOf(false)
+ flowOf(false),
)
assertTransition(
@@ -523,7 +504,7 @@ class LockscreenSceneTransitionInteractorTest : SysuiTestCase() {
flowOf(Scenes.Lockscreen),
progress,
false,
- flowOf(false)
+ flowOf(false),
)
assertTransition(
@@ -577,11 +558,7 @@ class LockscreenSceneTransitionInteractorTest : SysuiTestCase() {
)
progress.value = 0.4f
- assertTransition(
- step = currentStep!!,
- state = TransitionState.RUNNING,
- progress = 0.4f,
- )
+ assertTransition(step = currentStep!!, state = TransitionState.RUNNING, progress = 0.4f)
kosmos.realKeyguardTransitionRepository.startTransition(
TransitionInfo(
@@ -589,7 +566,7 @@ class LockscreenSceneTransitionInteractorTest : SysuiTestCase() {
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.AOD,
animator = null,
- modeOnCanceled = TransitionModeOnCanceled.RESET
+ modeOnCanceled = TransitionModeOnCanceled.RESET,
)
)
@@ -641,11 +618,7 @@ class LockscreenSceneTransitionInteractorTest : SysuiTestCase() {
)
progress.value = 0.4f
- assertTransition(
- step = currentStep!!,
- state = TransitionState.RUNNING,
- progress = 0.4f,
- )
+ assertTransition(step = currentStep!!, state = TransitionState.RUNNING, progress = 0.4f)
kosmos.realKeyguardTransitionRepository.startTransition(
TransitionInfo(
@@ -653,7 +626,7 @@ class LockscreenSceneTransitionInteractorTest : SysuiTestCase() {
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.AOD,
animator = null,
- modeOnCanceled = TransitionModeOnCanceled.RESET
+ modeOnCanceled = TransitionModeOnCanceled.RESET,
)
)
@@ -702,11 +675,7 @@ class LockscreenSceneTransitionInteractorTest : SysuiTestCase() {
)
progress.value = 0.4f
- assertTransition(
- step = currentStep!!,
- state = TransitionState.RUNNING,
- progress = 0.4f,
- )
+ assertTransition(step = currentStep!!, state = TransitionState.RUNNING, progress = 0.4f)
kosmos.realKeyguardTransitionRepository.startTransition(
TransitionInfo(
@@ -714,7 +683,7 @@ class LockscreenSceneTransitionInteractorTest : SysuiTestCase() {
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.AOD,
animator = null,
- modeOnCanceled = TransitionModeOnCanceled.RESET
+ modeOnCanceled = TransitionModeOnCanceled.RESET,
)
)
@@ -736,7 +705,7 @@ class LockscreenSceneTransitionInteractorTest : SysuiTestCase() {
flowOf(Scenes.Lockscreen),
progress,
false,
- flowOf(false)
+ flowOf(false),
)
assertTransition(
@@ -777,7 +746,7 @@ class LockscreenSceneTransitionInteractorTest : SysuiTestCase() {
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.AOD,
animator = null,
- modeOnCanceled = TransitionModeOnCanceled.RESET
+ modeOnCanceled = TransitionModeOnCanceled.RESET,
)
)
@@ -799,7 +768,7 @@ class LockscreenSceneTransitionInteractorTest : SysuiTestCase() {
flowOf(Scenes.Lockscreen),
progress,
false,
- flowOf(false)
+ flowOf(false),
)
allSteps[allSteps.size - 3]
@@ -858,7 +827,7 @@ class LockscreenSceneTransitionInteractorTest : SysuiTestCase() {
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.AOD,
animator = null,
- modeOnCanceled = TransitionModeOnCanceled.RESET
+ modeOnCanceled = TransitionModeOnCanceled.RESET,
)
)
@@ -880,7 +849,7 @@ class LockscreenSceneTransitionInteractorTest : SysuiTestCase() {
flowOf(Scenes.Lockscreen),
progress,
false,
- flowOf(false)
+ flowOf(false),
)
assertTransition(
@@ -959,7 +928,7 @@ class LockscreenSceneTransitionInteractorTest : SysuiTestCase() {
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.AOD,
animator = null,
- modeOnCanceled = TransitionModeOnCanceled.RESET
+ modeOnCanceled = TransitionModeOnCanceled.RESET,
)
)
@@ -977,7 +946,7 @@ class LockscreenSceneTransitionInteractorTest : SysuiTestCase() {
from = KeyguardState.AOD,
to = KeyguardState.DOZING,
animator = null,
- modeOnCanceled = TransitionModeOnCanceled.RESET
+ modeOnCanceled = TransitionModeOnCanceled.RESET,
)
)
@@ -995,7 +964,7 @@ class LockscreenSceneTransitionInteractorTest : SysuiTestCase() {
from = KeyguardState.DOZING,
to = KeyguardState.OCCLUDED,
animator = null,
- modeOnCanceled = TransitionModeOnCanceled.RESET
+ modeOnCanceled = TransitionModeOnCanceled.RESET,
)
)
@@ -1017,7 +986,7 @@ class LockscreenSceneTransitionInteractorTest : SysuiTestCase() {
flowOf(Scenes.Lockscreen),
progress,
false,
- flowOf(false)
+ flowOf(false),
)
assertTransition(
@@ -1077,7 +1046,7 @@ class LockscreenSceneTransitionInteractorTest : SysuiTestCase() {
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.AOD,
animator = null,
- modeOnCanceled = TransitionModeOnCanceled.RESET
+ modeOnCanceled = TransitionModeOnCanceled.RESET,
)
)
@@ -1092,7 +1061,7 @@ class LockscreenSceneTransitionInteractorTest : SysuiTestCase() {
kosmos.realKeyguardTransitionRepository.updateTransition(
ktfUuid!!,
1f,
- TransitionState.FINISHED
+ TransitionState.FINISHED,
)
assertTransition(
@@ -1110,7 +1079,7 @@ class LockscreenSceneTransitionInteractorTest : SysuiTestCase() {
flowOf(Scenes.Lockscreen),
progress,
false,
- flowOf(false)
+ flowOf(false),
)
assertTransition(
@@ -1171,7 +1140,7 @@ class LockscreenSceneTransitionInteractorTest : SysuiTestCase() {
flowOf(Scenes.Lockscreen),
progress,
false,
- flowOf(false)
+ flowOf(false),
)
assertTransition(
@@ -1235,7 +1204,7 @@ class LockscreenSceneTransitionInteractorTest : SysuiTestCase() {
flowOf(Scenes.Lockscreen),
progress,
false,
- flowOf(false)
+ flowOf(false),
)
assertTransition(
@@ -1291,7 +1260,7 @@ class LockscreenSceneTransitionInteractorTest : SysuiTestCase() {
flowOf(Scenes.Lockscreen),
progress,
false,
- flowOf(false)
+ flowOf(false),
)
assertTransition(
@@ -1308,7 +1277,7 @@ class LockscreenSceneTransitionInteractorTest : SysuiTestCase() {
from: KeyguardState? = null,
to: KeyguardState? = null,
state: TransitionState? = null,
- progress: Float? = null
+ progress: Float? = null,
) {
if (from != null) assertThat(step.from).isEqualTo(from)
if (to != null) assertThat(step.to).isEqualTo(to)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt
index d32d8cc4bd51..fb376ce3ca40 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt
@@ -1890,7 +1890,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa
// Callback gets an updated state
val state = PlaybackState.Builder().setState(PlaybackState.STATE_PLAYING, 0L, 1f).build()
- stateCallbackCaptor.value.invoke(KEY, state)
+ onStateUpdated(KEY, state)
// Listener is notified of updated state
verify(listener)
@@ -1911,7 +1911,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa
// No media added with this key
- stateCallbackCaptor.value.invoke(KEY, state)
+ onStateUpdated(KEY, state)
verify(listener, never())
.onMediaDataLoaded(eq(KEY), any(), any(), anyBoolean(), anyInt(), anyBoolean())
}
@@ -1928,7 +1928,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa
val state = PlaybackState.Builder().build()
// Then no changes are made
- stateCallbackCaptor.value.invoke(KEY, state)
+ onStateUpdated(KEY, state)
verify(listener, never())
.onMediaDataLoaded(eq(KEY), any(), any(), anyBoolean(), anyInt(), anyBoolean())
}
@@ -1939,7 +1939,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa
whenever(controller.playbackState).thenReturn(state)
addNotificationAndLoad()
- stateCallbackCaptor.value.invoke(KEY, state)
+ onStateUpdated(KEY, state)
verify(listener)
.onMediaDataLoaded(
@@ -1983,7 +1983,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa
backgroundExecutor.runAllReady()
foregroundExecutor.runAllReady()
- stateCallbackCaptor.value.invoke(PACKAGE_NAME, state)
+ onStateUpdated(PACKAGE_NAME, state)
verify(listener)
.onMediaDataLoaded(
@@ -2008,7 +2008,7 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa
.build()
addNotificationAndLoad()
- stateCallbackCaptor.value.invoke(KEY, state)
+ onStateUpdated(KEY, state)
verify(listener)
.onMediaDataLoaded(
@@ -2518,4 +2518,10 @@ class LegacyMediaDataManagerImplTest(flags: FlagsParameterization) : SysuiTestCa
eq(false),
)
}
+
+ private fun onStateUpdated(key: String, state: PlaybackState) {
+ stateCallbackCaptor.value.invoke(key, state)
+ backgroundExecutor.runAllReady()
+ foregroundExecutor.runAllReady()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
index 90af93292de1..7d364bd832f2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
@@ -1967,7 +1967,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() {
// Callback gets an updated state
val state = PlaybackState.Builder().setState(PlaybackState.STATE_PLAYING, 0L, 1f).build()
- stateCallbackCaptor.value.invoke(KEY, state)
+ testScope.onStateUpdated(KEY, state)
// Listener is notified of updated state
verify(listener)
@@ -1988,7 +1988,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() {
// No media added with this key
- stateCallbackCaptor.value.invoke(KEY, state)
+ testScope.onStateUpdated(KEY, state)
verify(listener, never())
.onMediaDataLoaded(eq(KEY), any(), any(), anyBoolean(), anyInt(), anyBoolean())
}
@@ -2005,7 +2005,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() {
val state = PlaybackState.Builder().build()
// Then no changes are made
- stateCallbackCaptor.value.invoke(KEY, state)
+ testScope.onStateUpdated(KEY, state)
verify(listener, never())
.onMediaDataLoaded(eq(KEY), any(), any(), anyBoolean(), anyInt(), anyBoolean())
}
@@ -2016,7 +2016,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() {
whenever(controller.playbackState).thenReturn(state)
addNotificationAndLoad()
- stateCallbackCaptor.value.invoke(KEY, state)
+ testScope.onStateUpdated(KEY, state)
verify(listener)
.onMediaDataLoaded(
@@ -2059,7 +2059,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() {
backgroundExecutor.runAllReady()
foregroundExecutor.runAllReady()
- stateCallbackCaptor.value.invoke(PACKAGE_NAME, state)
+ testScope.onStateUpdated(PACKAGE_NAME, state)
verify(listener)
.onMediaDataLoaded(
@@ -2084,7 +2084,7 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() {
.build()
addNotificationAndLoad()
- stateCallbackCaptor.value.invoke(KEY, state)
+ testScope.onStateUpdated(KEY, state)
verify(listener)
.onMediaDataLoaded(
@@ -2603,4 +2603,11 @@ class MediaDataProcessorTest(flags: FlagsParameterization) : SysuiTestCase() {
eq(false),
)
}
+
+ /** Helper function to update state and run executors */
+ private fun TestScope.onStateUpdated(key: String, state: PlaybackState) {
+ stateCallbackCaptor.value.invoke(key, state)
+ runCurrent()
+ advanceUntilIdle()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt
index 680df1584f89..dcf32a5f574d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt
@@ -137,7 +137,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
MediaTestUtils.emptyMediaData.copy(
app = PACKAGE,
packageName = PACKAGE,
- token = session.sessionToken
+ token = session.sessionToken,
)
resumeData = mediaData.copy(token = null, active = false, resumption = true)
@@ -237,7 +237,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
// Assuming we're registered
testOnMediaDataLoaded_registersPlaybackListener()
- mediaCallbackCaptor.value.onPlaybackStateChanged(
+ onPlaybackStateChanged(
PlaybackState.Builder().setState(PlaybackState.STATE_PAUSED, 0L, 0f).build()
)
assertThat(mainExecutor.numPending()).isEqualTo(1)
@@ -249,7 +249,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
// Assuming we have a pending timeout
testOnPlaybackStateChanged_schedulesTimeout_whenPaused()
- mediaCallbackCaptor.value.onPlaybackStateChanged(
+ onPlaybackStateChanged(
PlaybackState.Builder().setState(PlaybackState.STATE_PLAYING, 0L, 0f).build()
)
assertThat(mainExecutor.numPending()).isEqualTo(0)
@@ -261,7 +261,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
// Assuming we have a pending timeout
testOnPlaybackStateChanged_schedulesTimeout_whenPaused()
- mediaCallbackCaptor.value.onPlaybackStateChanged(
+ onPlaybackStateChanged(
PlaybackState.Builder().setState(PlaybackState.STATE_STOPPED, 0L, 0f).build()
)
assertThat(mainExecutor.numPending()).isEqualTo(1)
@@ -435,7 +435,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
// When the playback state changes, and has different actions
val playingState = PlaybackState.Builder().setActions(PlaybackState.ACTION_PLAY).build()
- mediaCallbackCaptor.value.onPlaybackStateChanged(playingState)
+ onPlaybackStateChanged(playingState)
assertThat(uiExecutor.runAllReady()).isEqualTo(1)
// Then the callback is invoked
@@ -448,7 +448,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
PlaybackState.CustomAction.Builder(
"ACTION_1",
"custom action 1",
- android.R.drawable.ic_media_ff
+ android.R.drawable.ic_media_ff,
)
.build()
val pausedState =
@@ -463,7 +463,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
PlaybackState.CustomAction.Builder(
"ACTION_2",
"custom action 2",
- android.R.drawable.ic_media_rew
+ android.R.drawable.ic_media_rew,
)
.build()
val pausedStateTwoActions =
@@ -472,7 +472,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
.addCustomAction(customOne)
.addCustomAction(customTwo)
.build()
- mediaCallbackCaptor.value.onPlaybackStateChanged(pausedStateTwoActions)
+ onPlaybackStateChanged(pausedStateTwoActions)
assertThat(uiExecutor.runAllReady()).isEqualTo(1)
// Then the callback is invoked
@@ -485,7 +485,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
loadMediaDataWithPlaybackState(stateWithActions)
// When the playback state updates with the same actions
- mediaCallbackCaptor.value.onPlaybackStateChanged(stateWithActions)
+ onPlaybackStateChanged(stateWithActions)
// Then the callback is not invoked again
verify(stateCallback, never()).invoke(eq(KEY), any())
@@ -512,7 +512,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
.setActions(PlaybackState.ACTION_PAUSE)
.addCustomAction(customTwo)
.build()
- mediaCallbackCaptor.value.onPlaybackStateChanged(stateTwo)
+ onPlaybackStateChanged(stateTwo)
// Then the callback is not invoked
verify(stateCallback, never()).invoke(eq(KEY), any())
@@ -544,7 +544,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
// When the playback state changes to playing
val playingState =
PlaybackState.Builder().setState(PlaybackState.STATE_PLAYING, 0L, 1f).build()
- mediaCallbackCaptor.value.onPlaybackStateChanged(playingState)
+ onPlaybackStateChanged(playingState)
uiExecutor.runAllReady()
// Then the callback is invoked
@@ -561,7 +561,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
// When the playback state is updated, but still not playing
val playingState =
PlaybackState.Builder().setState(PlaybackState.STATE_STOPPED, 0L, 0f).build()
- mediaCallbackCaptor.value.onPlaybackStateChanged(playingState)
+ onPlaybackStateChanged(playingState)
// Then the callback is not invoked
verify(stateCallback, never()).invoke(eq(KEY), eq(playingState!!))
@@ -571,7 +571,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
fun testTimeoutCallback_dozedPastTimeout_invokedOnWakeup() {
// When paused media is loaded
testOnMediaDataLoaded_registersPlaybackListener()
- mediaCallbackCaptor.value.onPlaybackStateChanged(
+ onPlaybackStateChanged(
PlaybackState.Builder().setState(PlaybackState.STATE_PAUSED, 0L, 0f).build()
)
verify(statusBarStateController).addCallback(capture(dozingCallbackCaptor))
@@ -597,7 +597,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
val time = clock.currentTimeMillis()
clock.setElapsedRealtime(time)
testOnMediaDataLoaded_registersPlaybackListener()
- mediaCallbackCaptor.value.onPlaybackStateChanged(
+ onPlaybackStateChanged(
PlaybackState.Builder().setState(PlaybackState.STATE_PAUSED, 0L, 0f).build()
)
verify(statusBarStateController).addCallback(capture(dozingCallbackCaptor))
@@ -706,4 +706,9 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
bgExecutor.runAllReady()
uiExecutor.runAllReady()
}
+
+ private fun onPlaybackStateChanged(state: PlaybackState) {
+ mediaCallbackCaptor.value.onPlaybackStateChanged(state)
+ bgExecutor.runAllReady()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
index 03667cfb8a3b..570c64065c4a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
@@ -21,19 +21,19 @@ import android.content.res.ColorStateList
import android.content.res.Configuration
import android.database.ContentObserver
import android.os.LocaleList
+import android.platform.test.flag.junit.FlagsParameterization
import android.provider.Settings
import android.testing.TestableLooper
import android.util.MathUtils.abs
import android.view.View
-import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.SceneKey
import com.android.internal.logging.InstanceId
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.systemui.Flags.mediaControlsUmoInflationInBackground
import com.android.systemui.SysuiTestCase
-import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.DisableSceneContainer
@@ -71,7 +71,6 @@ import com.android.systemui.statusbar.notification.collection.provider.OnReorder
import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.testKosmos
-import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.settings.GlobalSettings
import com.android.systemui.util.settings.unconfinedDispatcherFakeSettings
@@ -106,6 +105,8 @@ import org.mockito.MockitoAnnotations
import org.mockito.kotlin.any
import org.mockito.kotlin.capture
import org.mockito.kotlin.eq
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
private val DATA = MediaTestUtils.emptyMediaData
@@ -116,8 +117,8 @@ private const val PLAYING_LOCAL = "playing local"
@ExperimentalCoroutinesApi
@SmallTest
@TestableLooper.RunWithLooper(setAsMainLooper = true)
-@RunWith(AndroidJUnit4::class)
-class MediaCarouselControllerTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class MediaCarouselControllerTest(flags: FlagsParameterization) : SysuiTestCase() {
private val kosmos = testKosmos()
private val testDispatcher = kosmos.unconfinedTestDispatcher
private val secureSettings = kosmos.unconfinedDispatcherFakeSettings
@@ -129,7 +130,6 @@ class MediaCarouselControllerTest : SysuiTestCase() {
@Mock lateinit var mediaHostStatesManager: MediaHostStatesManager
@Mock lateinit var mediaHostState: MediaHostState
@Mock lateinit var activityStarter: ActivityStarter
- @Mock @Main private lateinit var executor: DelayableExecutor
@Mock lateinit var mediaDataManager: MediaDataManager
@Mock lateinit var configurationController: ConfigurationController
@Mock lateinit var falsingManager: FalsingManager
@@ -153,16 +153,33 @@ class MediaCarouselControllerTest : SysuiTestCase() {
private val clock = FakeSystemClock()
private lateinit var bgExecutor: FakeExecutor
+ private lateinit var uiExecutor: FakeExecutor
private lateinit var mediaCarouselController: MediaCarouselController
private var originalResumeSetting =
Settings.Secure.getInt(context.contentResolver, Settings.Secure.MEDIA_CONTROLS_RESUME, 1)
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.progressionOf(
+ com.android.systemui.Flags.FLAG_MEDIA_CONTROLS_UMO_INFLATION_IN_BACKGROUND
+ )
+ }
+ }
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags)
+ }
+
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
context.resources.configuration.setLocales(LocaleList(Locale.US, Locale.UK))
bgExecutor = FakeExecutor(clock)
+ uiExecutor = FakeExecutor(clock)
+
mediaCarouselController =
MediaCarouselController(
applicationScope = kosmos.applicationCoroutineScope,
@@ -173,7 +190,7 @@ class MediaCarouselControllerTest : SysuiTestCase() {
activityStarter = activityStarter,
systemClock = clock,
mainDispatcher = kosmos.testDispatcher,
- executor = executor,
+ uiExecutor = uiExecutor,
bgExecutor = bgExecutor,
backgroundDispatcher = testDispatcher,
mediaManager = mediaDataManager,
@@ -201,10 +218,11 @@ class MediaCarouselControllerTest : SysuiTestCase() {
whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(false)
MediaPlayerData.clear()
FakeExecutor.exhaustExecutors(bgExecutor)
+ FakeExecutor.exhaustExecutors(uiExecutor)
verify(globalSettings)
.registerContentObserverSync(
eq(Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE)),
- capture(settingsObserverCaptor)
+ capture(settingsObserverCaptor),
)
}
@@ -213,7 +231,7 @@ class MediaCarouselControllerTest : SysuiTestCase() {
Settings.Secure.putInt(
context.contentResolver,
Settings.Secure.MEDIA_CONTROLS_RESUME,
- originalResumeSetting
+ originalResumeSetting,
)
}
@@ -227,9 +245,9 @@ class MediaCarouselControllerTest : SysuiTestCase() {
active = true,
isPlaying = true,
playbackLocation = MediaData.PLAYBACK_LOCAL,
- resumption = false
+ resumption = false,
),
- 4500L
+ 4500L,
)
val playingCast =
@@ -239,9 +257,9 @@ class MediaCarouselControllerTest : SysuiTestCase() {
active = true,
isPlaying = true,
playbackLocation = MediaData.PLAYBACK_CAST_LOCAL,
- resumption = false
+ resumption = false,
),
- 5000L
+ 5000L,
)
val pausedLocal =
@@ -251,9 +269,9 @@ class MediaCarouselControllerTest : SysuiTestCase() {
active = true,
isPlaying = false,
playbackLocation = MediaData.PLAYBACK_LOCAL,
- resumption = false
+ resumption = false,
),
- 1000L
+ 1000L,
)
val pausedCast =
@@ -263,9 +281,9 @@ class MediaCarouselControllerTest : SysuiTestCase() {
active = true,
isPlaying = false,
playbackLocation = MediaData.PLAYBACK_CAST_LOCAL,
- resumption = false
+ resumption = false,
),
- 2000L
+ 2000L,
)
val playingRcn =
@@ -275,9 +293,9 @@ class MediaCarouselControllerTest : SysuiTestCase() {
active = true,
isPlaying = true,
playbackLocation = MediaData.PLAYBACK_CAST_REMOTE,
- resumption = false
+ resumption = false,
),
- 5000L
+ 5000L,
)
val pausedRcn =
@@ -287,9 +305,9 @@ class MediaCarouselControllerTest : SysuiTestCase() {
active = true,
isPlaying = false,
playbackLocation = MediaData.PLAYBACK_CAST_REMOTE,
- resumption = false
+ resumption = false,
),
- 5000L
+ 5000L,
)
val active =
@@ -299,9 +317,9 @@ class MediaCarouselControllerTest : SysuiTestCase() {
active = true,
isPlaying = false,
playbackLocation = MediaData.PLAYBACK_LOCAL,
- resumption = true
+ resumption = true,
),
- 250L
+ 250L,
)
val resume1 =
@@ -311,9 +329,9 @@ class MediaCarouselControllerTest : SysuiTestCase() {
active = false,
isPlaying = false,
playbackLocation = MediaData.PLAYBACK_LOCAL,
- resumption = true
+ resumption = true,
),
- 500L
+ 500L,
)
val resume2 =
@@ -323,9 +341,9 @@ class MediaCarouselControllerTest : SysuiTestCase() {
active = false,
isPlaying = false,
playbackLocation = MediaData.PLAYBACK_LOCAL,
- resumption = true
+ resumption = true,
),
- 1000L
+ 1000L,
)
val activeMoreRecent =
@@ -336,9 +354,9 @@ class MediaCarouselControllerTest : SysuiTestCase() {
isPlaying = false,
playbackLocation = MediaData.PLAYBACK_LOCAL,
resumption = true,
- lastActive = 2L
+ lastActive = 2L,
),
- 1000L
+ 1000L,
)
val activeLessRecent =
@@ -349,9 +367,9 @@ class MediaCarouselControllerTest : SysuiTestCase() {
isPlaying = false,
playbackLocation = MediaData.PLAYBACK_LOCAL,
resumption = true,
- lastActive = 1L
+ lastActive = 1L,
),
- 1000L
+ 1000L,
)
// Expected ordering for media players:
// Actively playing local sessions
@@ -370,7 +388,7 @@ class MediaCarouselControllerTest : SysuiTestCase() {
pausedRcn,
active,
resume2,
- resume1
+ resume1,
)
expected.forEach {
@@ -380,7 +398,7 @@ class MediaCarouselControllerTest : SysuiTestCase() {
it.second.copy(notificationKey = it.first),
panel,
clock,
- isSsReactivated = false
+ isSsReactivated = false,
)
}
@@ -403,7 +421,7 @@ class MediaCarouselControllerTest : SysuiTestCase() {
EMPTY_SMARTSPACE_MEDIA_DATA.copy(isActive = true),
panel,
true,
- clock
+ clock,
)
// Then it should be shown immediately after any actively playing controls
@@ -421,7 +439,7 @@ class MediaCarouselControllerTest : SysuiTestCase() {
listener.value.onSmartspaceMediaDataLoaded(
SMARTSPACE_KEY,
EMPTY_SMARTSPACE_MEDIA_DATA.copy(isActive = true),
- true
+ true,
)
// Then it should be shown immediately after any actively playing controls
@@ -439,7 +457,7 @@ class MediaCarouselControllerTest : SysuiTestCase() {
EMPTY_SMARTSPACE_MEDIA_DATA.copy(isActive = true),
panel,
false,
- clock
+ clock,
)
// Then it should be shown at the end of the carousel's active entries
@@ -461,8 +479,8 @@ class MediaCarouselControllerTest : SysuiTestCase() {
active = true,
isPlaying = true,
playbackLocation = MediaData.PLAYBACK_LOCAL,
- resumption = false
- )
+ resumption = false,
+ ),
)
listener.value.onMediaDataLoaded(
PLAYING_LOCAL,
@@ -471,19 +489,20 @@ class MediaCarouselControllerTest : SysuiTestCase() {
active = true,
isPlaying = false,
playbackLocation = MediaData.PLAYBACK_LOCAL,
- resumption = true
- )
+ resumption = true,
+ ),
)
+ runAllReady()
assertEquals(
MediaPlayerData.getMediaPlayerIndex(PAUSED_LOCAL),
- mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex
+ mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex,
)
// paused player order should stays the same in visibleMediaPLayer map.
// paused player order should be first in mediaPlayer map.
assertEquals(
MediaPlayerData.visiblePlayerKeys().elementAt(3),
- MediaPlayerData.playerKeys().elementAt(0)
+ MediaPlayerData.playerKeys().elementAt(0),
)
}
@@ -506,7 +525,7 @@ class MediaCarouselControllerTest : SysuiTestCase() {
mediaCarouselController.onDesiredLocationChanged(
LOCATION_QS,
mediaHostState,
- animate = false
+ animate = false,
)
bgExecutor.runAllReady()
verify(logger).logCarouselPosition(LOCATION_QS)
@@ -517,7 +536,7 @@ class MediaCarouselControllerTest : SysuiTestCase() {
mediaCarouselController.onDesiredLocationChanged(
MediaHierarchyManager.LOCATION_QQS,
mediaHostState,
- animate = false
+ animate = false,
)
bgExecutor.runAllReady()
verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_QQS)
@@ -528,7 +547,7 @@ class MediaCarouselControllerTest : SysuiTestCase() {
mediaCarouselController.onDesiredLocationChanged(
MediaHierarchyManager.LOCATION_LOCKSCREEN,
mediaHostState,
- animate = false
+ animate = false,
)
bgExecutor.runAllReady()
verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_LOCKSCREEN)
@@ -539,7 +558,7 @@ class MediaCarouselControllerTest : SysuiTestCase() {
mediaCarouselController.onDesiredLocationChanged(
MediaHierarchyManager.LOCATION_DREAM_OVERLAY,
mediaHostState,
- animate = false
+ animate = false,
)
bgExecutor.runAllReady()
verify(logger).logCarouselPosition(MediaHierarchyManager.LOCATION_DREAM_OVERLAY)
@@ -570,8 +589,8 @@ class MediaCarouselControllerTest : SysuiTestCase() {
active = true,
isPlaying = true,
playbackLocation = MediaData.PLAYBACK_LOCAL,
- resumption = false
- )
+ resumption = false,
+ ),
)
listener.value.onMediaDataLoaded(
PAUSED_LOCAL,
@@ -580,14 +599,15 @@ class MediaCarouselControllerTest : SysuiTestCase() {
active = true,
isPlaying = false,
playbackLocation = MediaData.PLAYBACK_LOCAL,
- resumption = false
- )
+ resumption = false,
+ ),
)
+ runAllReady()
// adding a media recommendation card.
listener.value.onSmartspaceMediaDataLoaded(
SMARTSPACE_KEY,
EMPTY_SMARTSPACE_MEDIA_DATA,
- false
+ false,
)
mediaCarouselController.shouldScrollToKey = true
// switching between media players.
@@ -598,8 +618,8 @@ class MediaCarouselControllerTest : SysuiTestCase() {
active = true,
isPlaying = false,
playbackLocation = MediaData.PLAYBACK_LOCAL,
- resumption = true
- )
+ resumption = true,
+ ),
)
listener.value.onMediaDataLoaded(
PAUSED_LOCAL,
@@ -608,13 +628,14 @@ class MediaCarouselControllerTest : SysuiTestCase() {
active = true,
isPlaying = true,
playbackLocation = MediaData.PLAYBACK_LOCAL,
- resumption = false
- )
+ resumption = false,
+ ),
)
+ runAllReady()
assertEquals(
MediaPlayerData.getMediaPlayerIndex(PAUSED_LOCAL),
- mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex
+ mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex,
)
}
@@ -626,7 +647,7 @@ class MediaCarouselControllerTest : SysuiTestCase() {
listener.value.onSmartspaceMediaDataLoaded(
SMARTSPACE_KEY,
EMPTY_SMARTSPACE_MEDIA_DATA.copy(packageName = "PACKAGE_NAME", isActive = true),
- false
+ false,
)
listener.value.onMediaDataLoaded(
PLAYING_LOCAL,
@@ -635,14 +656,15 @@ class MediaCarouselControllerTest : SysuiTestCase() {
active = true,
isPlaying = true,
playbackLocation = MediaData.PLAYBACK_LOCAL,
- resumption = false
- )
+ resumption = false,
+ ),
)
+ runAllReady()
var playerIndex = MediaPlayerData.getMediaPlayerIndex(PLAYING_LOCAL)
assertEquals(
playerIndex,
- mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex
+ mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex,
)
assertEquals(playerIndex, 0)
@@ -657,9 +679,10 @@ class MediaCarouselControllerTest : SysuiTestCase() {
isPlaying = true,
playbackLocation = MediaData.PLAYBACK_LOCAL,
resumption = false,
- packageName = "PACKAGE_NAME"
- )
+ packageName = "PACKAGE_NAME",
+ ),
)
+ runAllReady()
playerIndex = MediaPlayerData.getMediaPlayerIndex(PLAYING_LOCAL)
assertEquals(playerIndex, 0)
}
@@ -704,7 +727,7 @@ class MediaCarouselControllerTest : SysuiTestCase() {
player1.second.copy(notificationKey = player1.first),
panel,
clock,
- isSsReactivated = false
+ isSsReactivated = false,
)
assertEquals(mediaCarouselController.getCurrentVisibleMediaContentIntent(), clickIntent1)
@@ -717,7 +740,7 @@ class MediaCarouselControllerTest : SysuiTestCase() {
player2.second.copy(notificationKey = player2.first),
panel,
clock,
- isSsReactivated = false
+ isSsReactivated = false,
)
// mediaCarouselScrollHandler.visibleMediaIndex is unchanged (= 0), and the new player is
@@ -732,7 +755,7 @@ class MediaCarouselControllerTest : SysuiTestCase() {
player3.second.copy(notificationKey = player3.first),
panel,
clock,
- isSsReactivated = false
+ isSsReactivated = false,
)
// mediaCarouselScrollHandler.visibleMediaIndex is unchanged (= 0), and the new player is
@@ -822,7 +845,7 @@ class MediaCarouselControllerTest : SysuiTestCase() {
listener.value.onSmartspaceMediaDataLoaded(
SMARTSPACE_KEY,
EMPTY_SMARTSPACE_MEDIA_DATA.copy(isActive = true),
- true
+ true,
)
// Then the carousel is updated
@@ -841,7 +864,7 @@ class MediaCarouselControllerTest : SysuiTestCase() {
listener.value.onSmartspaceMediaDataLoaded(
SMARTSPACE_KEY,
EMPTY_SMARTSPACE_MEDIA_DATA,
- false
+ false,
)
// Then it is added to the carousel with correct state
@@ -886,7 +909,7 @@ class MediaCarouselControllerTest : SysuiTestCase() {
transitionRepository.sendTransitionSteps(
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.GONE,
- this
+ this,
)
verify(mediaCarousel).visibility = View.VISIBLE
@@ -932,7 +955,7 @@ class MediaCarouselControllerTest : SysuiTestCase() {
transitionRepository.sendTransitionSteps(
from = KeyguardState.GONE,
to = KeyguardState.LOCKSCREEN,
- this
+ this,
)
assertEquals(true, updatedVisibility)
@@ -961,7 +984,7 @@ class MediaCarouselControllerTest : SysuiTestCase() {
transitionRepository.sendTransitionSteps(
from = KeyguardState.GONE,
to = KeyguardState.LOCKSCREEN,
- this
+ this,
)
assertEquals(true, updatedVisibility)
@@ -1125,12 +1148,14 @@ class MediaCarouselControllerTest : SysuiTestCase() {
Settings.Secure.putInt(context.contentResolver, Settings.Secure.MEDIA_CONTROLS_RESUME, 0)
val pausedMedia = DATA.copy(isPlaying = false)
listener.value.onMediaDataLoaded(PAUSED_LOCAL, PAUSED_LOCAL, pausedMedia)
+ runAllReady()
mediaCarouselController.onSwipeToDismiss()
// When it can be removed immediately on update
whenever(visualStabilityProvider.isReorderingAllowed).thenReturn(true)
val inactiveMedia = pausedMedia.copy(active = false)
listener.value.onMediaDataLoaded(PAUSED_LOCAL, PAUSED_LOCAL, inactiveMedia)
+ runAllReady()
// This is processed as a user-initiated dismissal
verify(debugLogger).logMediaRemoved(eq(PAUSED_LOCAL), eq(true))
@@ -1148,12 +1173,14 @@ class MediaCarouselControllerTest : SysuiTestCase() {
val pausedMedia = DATA.copy(isPlaying = false)
listener.value.onMediaDataLoaded(PAUSED_LOCAL, PAUSED_LOCAL, pausedMedia)
+ runAllReady()
mediaCarouselController.onSwipeToDismiss()
// When it can't be removed immediately on update
whenever(visualStabilityProvider.isReorderingAllowed).thenReturn(false)
val inactiveMedia = pausedMedia.copy(active = false)
listener.value.onMediaDataLoaded(PAUSED_LOCAL, PAUSED_LOCAL, inactiveMedia)
+ runAllReady()
visualStabilityCallback.value.onReorderingAllowed()
// This is processed as a user-initiated dismissal
@@ -1175,8 +1202,8 @@ class MediaCarouselControllerTest : SysuiTestCase() {
active = true,
isPlaying = true,
playbackLocation = MediaData.PLAYBACK_LOCAL,
- resumption = false
- )
+ resumption = false,
+ ),
)
listener.value.onMediaDataLoaded(
PAUSED_LOCAL,
@@ -1185,18 +1212,20 @@ class MediaCarouselControllerTest : SysuiTestCase() {
active = true,
isPlaying = false,
playbackLocation = MediaData.PLAYBACK_LOCAL,
- resumption = false
- )
+ resumption = false,
+ ),
)
+ runAllReady()
val playersSize = MediaPlayerData.players().size
reset(pageIndicator)
function()
+ runAllReady()
assertEquals(playersSize, MediaPlayerData.players().size)
assertEquals(
MediaPlayerData.getMediaPlayerIndex(PLAYING_LOCAL),
- mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex
+ mediaCarouselController.mediaCarouselScrollHandler.visibleMediaIndex,
)
}
@@ -1225,4 +1254,11 @@ class MediaCarouselControllerTest : SysuiTestCase() {
)
runCurrent()
}
+
+ private fun runAllReady() {
+ if (mediaControlsUmoInflationInBackground()) {
+ bgExecutor.runAllReady()
+ uiExecutor.runAllReady()
+ }
+ }
}
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 c4f5d621cb6c..07e48b9da153 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
@@ -30,7 +30,7 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.app.KeyguardManager;
@@ -313,7 +313,7 @@ public class MediaSwitchingControllerTest extends SysuiTestCase {
mMediaSwitchingController.start(mCb);
verify(mSessionMediaController, never()).registerCallback(any());
- verifyZeroInteractions(mMediaSessionManager);
+ verifyNoMoreInteractions(mMediaSessionManager);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
index 80a9e4ce991b..d7084898a4a4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
@@ -83,7 +83,7 @@ import org.mockito.Mockito.doNothing
import org.mockito.Mockito.never
import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.MockitoAnnotations
import org.mockito.kotlin.whenever
@@ -177,7 +177,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
)
verify(eventLogger).logNoteTaskOpened(expectedInfo)
- verifyZeroInteractions(bubbles, keyguardManager, userManager)
+ verifyNoMoreInteractions(bubbles, keyguardManager, userManager)
}
@Test
@@ -192,7 +192,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
)
verify(eventLogger).logNoteTaskClosed(expectedInfo)
- verifyZeroInteractions(bubbles, keyguardManager, userManager)
+ verifyNoMoreInteractions(bubbles, keyguardManager, userManager)
}
@Test
@@ -206,7 +206,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
key = Bubble.getAppBubbleKeyForApp(expectedInfo.packageName, expectedInfo.user),
)
- verifyZeroInteractions(bubbles, keyguardManager, userManager, eventLogger)
+ verifyNoMoreInteractions(bubbles, keyguardManager, userManager, eventLogger)
}
@Test
@@ -220,7 +220,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
key = Bubble.getAppBubbleKeyForApp(expectedInfo.packageName, expectedInfo.user),
)
- verifyZeroInteractions(bubbles, keyguardManager, userManager, eventLogger)
+ verifyNoMoreInteractions(bubbles, keyguardManager, userManager, eventLogger)
}
@Test
@@ -231,7 +231,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
key = "any other key",
)
- verifyZeroInteractions(bubbles, keyguardManager, userManager, eventLogger)
+ verifyNoMoreInteractions(bubbles, keyguardManager, userManager, eventLogger)
}
@Test
@@ -242,7 +242,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
key = Bubble.getAppBubbleKeyForApp(NOTE_TASK_INFO.packageName, NOTE_TASK_INFO.user),
)
- verifyZeroInteractions(bubbles, keyguardManager, userManager, eventLogger)
+ verifyNoMoreInteractions(bubbles, keyguardManager, userManager, eventLogger)
}
// endregion
@@ -275,7 +275,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
}
assertThat(userCaptor.value).isEqualTo(user10)
verify(eventLogger).logNoteTaskOpened(expectedInfo)
- verifyZeroInteractions(bubbles)
+ verifyNoMoreInteractions(bubbles)
}
@Test
@@ -299,7 +299,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
}
assertThat(userCaptor.value).isEqualTo(userTracker.userHandle)
verify(eventLogger).logNoteTaskOpened(expectedInfo)
- verifyZeroInteractions(bubbles)
+ verifyNoMoreInteractions(bubbles)
}
@Test
@@ -322,7 +322,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
}
assertThat(userCaptor.value).isEqualTo(userTracker.userHandle)
verify(eventLogger).logNoteTaskClosed(expectedInfo)
- verifyZeroInteractions(bubbles)
+ verifyNoMoreInteractions(bubbles)
}
@Test
@@ -336,7 +336,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
// Context package name used to create bubble icon from drawable resource id
verify(context, atLeastOnce()).packageName
verifyNoteTaskOpenInBubbleInUser(userTracker.userHandle)
- verifyZeroInteractions(eventLogger)
+ verifyNoMoreInteractions(eventLogger)
}
@Test
@@ -352,7 +352,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
// Context package name used to create bubble icon from drawable resource id
verify(context, atLeastOnce()).packageName
verifyNoteTaskOpenInBubbleInUser(userTracker.userHandle)
- verifyZeroInteractions(eventLogger)
+ verifyNoMoreInteractions(eventLogger)
}
@Test
@@ -393,14 +393,14 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
}
assertThat(userCaptor.value).isEqualTo(user10)
verify(eventLogger).logNoteTaskOpened(expectedInfo)
- verifyZeroInteractions(bubbles)
+ verifyNoMoreInteractions(bubbles)
}
@Test
fun showNoteTask_bubblesIsNull_shouldDoNothing() {
createNoteTaskController(bubbles = null).showNoteTask(entryPoint = TAIL_BUTTON)
- verifyZeroInteractions(bubbles, eventLogger)
+ verifyNoMoreInteractions(bubbles, eventLogger)
}
@Test
@@ -412,14 +412,14 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
noteTaskController.showNoteTask(entryPoint = TAIL_BUTTON)
verify(noteTaskController).showNoDefaultNotesAppToast()
- verifyZeroInteractions(bubbles, eventLogger)
+ verifyNoMoreInteractions(bubbles, eventLogger)
}
@Test
fun showNoteTask_flagDisabled_shouldDoNothing() {
createNoteTaskController(isEnabled = false).showNoteTask(entryPoint = TAIL_BUTTON)
- verifyZeroInteractions(bubbles, eventLogger)
+ verifyNoMoreInteractions(bubbles, eventLogger)
}
@Test
@@ -428,7 +428,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
createNoteTaskController().showNoteTask(entryPoint = TAIL_BUTTON)
- verifyZeroInteractions(bubbles, eventLogger)
+ verifyNoMoreInteractions(bubbles, eventLogger)
}
@Test
@@ -453,7 +453,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
}
assertThat(userCaptor.value).isEqualTo(userTracker.userHandle)
verify(eventLogger).logNoteTaskOpened(expectedInfo)
- verifyZeroInteractions(bubbles)
+ verifyNoMoreInteractions(bubbles)
}
// endregion
@@ -550,7 +550,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
createNoteTaskController().showNoteTask(entryPoint = QUICK_AFFORDANCE)
- verifyZeroInteractions(bubbles, eventLogger)
+ verifyNoMoreInteractions(bubbles, eventLogger)
}
@Test
@@ -566,7 +566,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
createNoteTaskController().showNoteTask(entryPoint = QUICK_AFFORDANCE)
- verifyZeroInteractions(bubbles, eventLogger)
+ verifyNoMoreInteractions(bubbles, eventLogger)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
index ab846f143caf..d88b75896a58 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
@@ -56,7 +56,7 @@ import org.mockito.Mock
import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.MockitoAnnotations.initMocks
/** atest SystemUITests:NoteTaskInitializerTest */
@@ -135,7 +135,7 @@ internal class NoteTaskInitializerTest : SysuiTestCase() {
underTest.initialize()
- verifyZeroInteractions(
+ verifyNoMoreInteractions(
commandQueue,
bubbles,
controller,
@@ -151,7 +151,7 @@ internal class NoteTaskInitializerTest : SysuiTestCase() {
underTest.initialize()
- verifyZeroInteractions(
+ verifyNoMoreInteractions(
commandQueue,
bubbles,
controller,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java
index eea02eec7099..2f8f45cb0197 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java
@@ -29,6 +29,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -887,6 +888,34 @@ public class InternetDialogDelegateControllerTest extends SysuiTestCase {
}
@Test
+ public void getActiveAutoSwitchNonDdsSubId_registerCallbackForExistedSubId_notRegister() {
+ mFlags.set(Flags.QS_SECONDARY_DATA_SUB_INFO, true);
+
+ // Adds non DDS subId
+ SubscriptionInfo info = mock(SubscriptionInfo.class);
+ doReturn(SUB_ID2).when(info).getSubscriptionId();
+ doReturn(false).when(info).isOpportunistic();
+ when(mSubscriptionManager.getActiveSubscriptionInfo(anyInt())).thenReturn(info);
+
+ mInternetDialogController.getActiveAutoSwitchNonDdsSubId();
+
+ // 1st time is onStart(), 2nd time is getActiveAutoSwitchNonDdsSubId()
+ verify(mTelephonyManager, times(2)).registerTelephonyCallback(any(), any());
+ assertThat(mInternetDialogController.mSubIdTelephonyCallbackMap.size() == 2);
+
+ // Adds non DDS subId again
+ doReturn(SUB_ID2).when(info).getSubscriptionId();
+ doReturn(false).when(info).isOpportunistic();
+ when(mSubscriptionManager.getActiveSubscriptionInfo(anyInt())).thenReturn(info);
+
+ mInternetDialogController.getActiveAutoSwitchNonDdsSubId();
+
+ // Does not add due to cached subInfo in mSubIdTelephonyCallbackMap.
+ verify(mTelephonyManager, times(2)).registerTelephonyCallback(any(), any());
+ assertThat(mInternetDialogController.mSubIdTelephonyCallbackMap.size() == 2);
+ }
+
+ @Test
public void getMobileNetworkSummary() {
mFlags.set(Flags.QS_SECONDARY_DATA_SUB_INFO, true);
Resources res1 = mock(Resources.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index a6afd0e499f4..f5a901963f05 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -22,10 +22,6 @@ import static com.android.systemui.log.LogBufferHelperKt.logcatLogBuffer;
import static com.google.common.truth.Truth.assertThat;
-import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
-import static kotlinx.coroutines.flow.SharedFlowKt.MutableSharedFlow;
-import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow;
-
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyFloat;
@@ -40,6 +36,10 @@ import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
+import static kotlinx.coroutines.flow.SharedFlowKt.MutableSharedFlow;
+import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow;
+
import android.animation.Animator;
import android.annotation.IdRes;
import android.content.ContentResolver;
@@ -95,6 +95,7 @@ import com.android.systemui.flags.FakeFeatureFlagsClassic;
import com.android.systemui.flags.Flags;
import com.android.systemui.fragments.FragmentHostManager;
import com.android.systemui.fragments.FragmentService;
+import com.android.systemui.haptics.msdl.FakeMSDLPlayer;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.KeyguardViewConfigurator;
import com.android.systemui.keyguard.data.repository.FakeKeyguardClockRepository;
@@ -151,6 +152,7 @@ import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository;
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.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinatorLogger;
import com.android.systemui.statusbar.notification.data.repository.NotificationsKeyguardViewStateRepository;
@@ -167,7 +169,6 @@ import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.ConfigurationControllerImpl;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
-import com.android.systemui.statusbar.notification.HeadsUpTouchHelper;
import com.android.systemui.statusbar.phone.KeyguardBottomAreaView;
import com.android.systemui.statusbar.phone.KeyguardBottomAreaViewController;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
@@ -200,12 +201,6 @@ import com.android.systemui.util.time.FakeSystemClock;
import com.android.systemui.util.time.SystemClock;
import com.android.wm.shell.animation.FlingAnimationUtils;
-import dagger.Lazy;
-
-import kotlinx.coroutines.CoroutineDispatcher;
-import kotlinx.coroutines.channels.BufferOverflow;
-import kotlinx.coroutines.test.TestScope;
-
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
@@ -220,6 +215,11 @@ import java.util.HashSet;
import java.util.List;
import java.util.Optional;
+import dagger.Lazy;
+import kotlinx.coroutines.CoroutineDispatcher;
+import kotlinx.coroutines.channels.BufferOverflow;
+import kotlinx.coroutines.test.TestScope;
+
public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
protected static final int SPLIT_SHADE_FULL_TRANSITION_DISTANCE = 400;
@@ -374,6 +374,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
protected View.OnLayoutChangeListener mLayoutChangeListener;
protected KeyguardStatusViewController mKeyguardStatusViewController;
protected ShadeRepository mShadeRepository;
+ protected FakeMSDLPlayer mMSDLPlayer = mKosmos.getMsdlPlayer();
protected final FalsingManagerFake mFalsingManager = new FalsingManagerFake();
protected final Optional<SysUIUnfoldComponent> mSysUIUnfoldComponent = Optional.empty();
@@ -761,7 +762,8 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
new ResourcesSplitShadeStateController(),
mPowerInteractor,
mKeyguardClockPositionAlgorithm,
- mNaturalScrollingSettingObserver);
+ mNaturalScrollingSettingObserver,
+ mMSDLPlayer);
mNotificationPanelViewController.initDependencies(
mCentralSurfaces,
null,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index a7fd1609d1ca..43dbb40d7721 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -49,6 +49,7 @@ import android.os.PowerManager;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.testing.TestableLooper;
+import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
import android.view.View;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -69,6 +70,8 @@ import com.android.systemui.statusbar.notification.stack.AmbientState;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.phone.KeyguardClockPositionAlgorithm;
+import com.google.android.msdl.data.model.MSDLToken;
+
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
@@ -1458,4 +1461,23 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo
assertThat(mNotificationPanelViewController.getFalsingThreshold()).isGreaterThan(14);
}
+
+ @Test
+ @EnableFlags(com.android.systemui.Flags.FLAG_MSDL_FEEDBACK)
+ public void performHapticFeedback_withMSDL_forGestureStart_deliversDragThresholdToken() {
+ mNotificationPanelViewController
+ .performHapticFeedback(HapticFeedbackConstants.GESTURE_START);
+
+ assertThat(mMSDLPlayer.getLatestTokenPlayed())
+ .isEqualTo(MSDLToken.SWIPE_THRESHOLD_INDICATOR);
+ }
+
+ @Test
+ @EnableFlags(com.android.systemui.Flags.FLAG_MSDL_FEEDBACK)
+ public void performHapticFeedback_withMSDL_forReject_deliversFailureToken() {
+ mNotificationPanelViewController
+ .performHapticFeedback(HapticFeedbackConstants.REJECT);
+
+ assertThat(mMSDLPlayer.getLatestTokenPlayed()).isEqualTo(MSDLToken.FAILURE);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt
index 90655c3cf4b3..97441f01bcf5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt
@@ -33,7 +33,6 @@ import com.android.systemui.res.R
import com.android.systemui.statusbar.StatusBarState.KEYGUARD
import com.android.systemui.statusbar.StatusBarState.SHADE
import com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED
-import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRowRepository
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
@@ -157,6 +156,7 @@ class NotificationPanelViewControllerWithCoroutinesTest :
}
@Test
+ @DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
fun doubleTapRequired_onKeyguard_usesPerformHapticFeedback() = runTest {
launch(Dispatchers.Main.immediate) {
val listener = getFalsingTapListener()
@@ -184,6 +184,7 @@ class NotificationPanelViewControllerWithCoroutinesTest :
}
@Test
+ @DisableFlags(Flags.FLAG_MSDL_FEEDBACK)
fun doubleTapRequired_shadeLocked_usesPerformHapticFeedback() = runTest {
launch(Dispatchers.Main.immediate) {
val listener = getFalsingTapListener()
@@ -209,7 +210,7 @@ class NotificationPanelViewControllerWithCoroutinesTest :
KEYGUARD /*statusBarState*/,
false /*keyguardFadingAway*/,
false /*goingToFullShade*/,
- SHADE /*oldStatusBarState*/
+ SHADE, /*oldStatusBarState*/
)
}
advanceUntilIdle()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java
index 308b3708e407..9a8df33a0276 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java
@@ -31,7 +31,7 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.content.Context;
@@ -642,7 +642,7 @@ public class ShadeCarrierGroupControllerTest extends LeakCheckedTest {
when(mShadeCarrier1.isVisibleToUser()).thenReturn(false);
captor.getValue().onClick(mShadeCarrier1);
- verifyZeroInteractions(mActivityStarter);
+ verifyNoMoreInteractions(mActivityStarter);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
index d10ea1f02367..2b5e014b2048 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
@@ -26,7 +26,6 @@ import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
import android.content.ComponentName;
import android.graphics.Rect;
@@ -393,7 +392,7 @@ public class CommandQueueTest extends SysuiTestCase {
mCommandQueue.addQsTileToFrontOrEnd(c, true);
waitForIdleSync();
- verifyZeroInteractions(mCallbacks);
+ verifyNoMoreInteractions(mCallbacks);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsReceiverTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsReceiverTest.java
index e0eb99cebd37..2cb9791cc159 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsReceiverTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsReceiverTest.java
@@ -22,7 +22,7 @@ import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.content.Intent;
@@ -138,7 +138,7 @@ public class KeyboardShortcutsReceiverTest extends SysuiTestCase {
mKeyboardShortcutsReceiver.onReceive(mContext, SHOW_INTENT);
- verifyZeroInteractions(mKeyboardShortcuts, mKeyboardShortcutListSearch);
+ verifyNoMoreInteractions(mKeyboardShortcuts, mKeyboardShortcutListSearch);
}
@Test
@@ -149,7 +149,7 @@ public class KeyboardShortcutsReceiverTest extends SysuiTestCase {
mKeyboardShortcutsReceiver.onReceive(mContext, SHOW_INTENT);
- verifyZeroInteractions(mKeyboardShortcuts, mKeyboardShortcutListSearch);
+ verifyNoMoreInteractions(mKeyboardShortcuts, mKeyboardShortcutListSearch);
}
@Test
@@ -160,7 +160,7 @@ public class KeyboardShortcutsReceiverTest extends SysuiTestCase {
mKeyboardShortcutsReceiver.onReceive(mContext, DISMISS_INTENT);
- verifyZeroInteractions(mKeyboardShortcuts, mKeyboardShortcutListSearch);
+ verifyNoMoreInteractions(mKeyboardShortcuts, mKeyboardShortcutListSearch);
}
@Test
@@ -171,6 +171,6 @@ public class KeyboardShortcutsReceiverTest extends SysuiTestCase {
mKeyboardShortcutsReceiver.onReceive(mContext, DISMISS_INTENT);
- verifyZeroInteractions(mKeyboardShortcuts, mKeyboardShortcutListSearch);
+ verifyNoMoreInteractions(mKeyboardShortcuts, mKeyboardShortcutListSearch);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
index 9e6a498b325a..a8618eb544d4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
@@ -60,7 +60,7 @@ import org.mockito.Mockito
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.Mockito.`when` as whenever
import org.mockito.junit.MockitoJUnit
@@ -557,7 +557,7 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() {
transitionController.dragDownAmount = 10f
verify(singleShadeOverScroller).expansionDragDownAmount = 10f
- verifyZeroInteractions(splitShadeOverScroller)
+ verifyNoMoreInteractions(splitShadeOverScroller)
}
@Test
@@ -568,7 +568,7 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() {
transitionController.dragDownAmount = 10f
verify(splitShadeOverScroller).expansionDragDownAmount = 10f
- verifyZeroInteractions(singleShadeOverScroller)
+ verifyNoMoreInteractions(singleShadeOverScroller)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerTest.kt
index 58473c4e07a3..98487f7ac059 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerTest.kt
@@ -3,6 +3,7 @@ package com.android.systemui.statusbar
import android.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.qs.QS
@@ -19,7 +20,6 @@ import org.mockito.Mockito.intThat
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
-import org.mockito.Mockito.verifyZeroInteractions
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@@ -30,6 +30,7 @@ class SplitShadeLockScreenOverScrollerTest : SysuiTestCase() {
private val configurationController = FakeConfigurationController()
+ @OptIn(ExperimentalCoroutinesApi::class)
@Mock private lateinit var scrimController: ScrimController
@Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
private var qS: QS? = null
@@ -71,9 +72,9 @@ class SplitShadeLockScreenOverScrollerTest : SysuiTestCase() {
setDragAmount(1000f)
- verifyZeroInteractions(qS)
- verifyZeroInteractions(scrimController)
- verifyZeroInteractions(nsslController)
+ verifyNoMoreInteractions(qS)
+ verifyNoMoreInteractions(scrimController)
+ verifyNoMoreInteractions(nsslController)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/core/CommandQueueInitializerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/core/CommandQueueInitializerTest.kt
new file mode 100644
index 000000000000..2a196c6b979f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/core/CommandQueueInitializerTest.kt
@@ -0,0 +1,129 @@
+/*
+ * 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.core
+
+import android.internal.statusbar.fakeStatusBarService
+import android.platform.test.annotations.EnableFlags
+import android.view.WindowInsets
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.initController
+import com.android.systemui.keyguard.data.repository.fakeCommandQueue
+import com.android.systemui.statusbar.data.repository.fakeStatusBarModeRepository
+import com.android.systemui.statusbar.mockCommandQueueCallbacks
+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.verify
+
+@EnableFlags(StatusBarSimpleFragment.FLAG_NAME)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CommandQueueInitializerTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val initController = kosmos.initController
+ private val commandQueue = kosmos.fakeCommandQueue
+ private val commandQueueCallbacks = kosmos.mockCommandQueueCallbacks
+ private val statusBarModeRepository = kosmos.fakeStatusBarModeRepository
+ private val fakeStatusBarService = kosmos.fakeStatusBarService
+ private val initializer = kosmos.commandQueueInitializer
+
+ @Test
+ fun start_registersStatusBar() {
+ initializer.start()
+
+ assertThat(fakeStatusBarService.registeredStatusBar).isNotNull()
+ }
+
+ @Test
+ fun start_barResultHasTransientStatusBar_transientStateIsTrue() {
+ fakeStatusBarService.transientBarTypes = WindowInsets.Type.statusBars()
+
+ initializer.start()
+
+ assertThat(statusBarModeRepository.defaultDisplay.isTransientShown.value).isTrue()
+ }
+
+ @Test
+ fun start_barResultDoesNotHaveTransientStatusBar_transientStateIsFalse() {
+ fakeStatusBarService.transientBarTypes = WindowInsets.Type.navigationBars()
+
+ initializer.start()
+
+ assertThat(statusBarModeRepository.defaultDisplay.isTransientShown.value).isFalse()
+ }
+
+ @Test
+ fun start_callsOnSystemBarAttributesChanged_basedOnRegisterBarResult() {
+ initializer.start()
+
+ verify(commandQueueCallbacks)
+ .onSystemBarAttributesChanged(
+ context.displayId,
+ fakeStatusBarService.appearance,
+ fakeStatusBarService.appearanceRegions,
+ fakeStatusBarService.navbarColorManagedByIme,
+ fakeStatusBarService.behavior,
+ fakeStatusBarService.requestedVisibleTypes,
+ fakeStatusBarService.packageName,
+ fakeStatusBarService.letterboxDetails,
+ )
+ }
+
+ @Test
+ fun start_callsSetIcon_basedOnRegisterBarResult() {
+ initializer.start()
+
+ assertThat(commandQueue.icons).isEqualTo(fakeStatusBarService.statusBarIcons)
+ }
+
+ @Test
+ fun start_callsSetImeWindowStatus_basedOnRegisterBarResult() {
+ initializer.start()
+
+ verify(commandQueueCallbacks)
+ .setImeWindowStatus(
+ context.displayId,
+ fakeStatusBarService.imeWindowVis,
+ fakeStatusBarService.imeBackDisposition,
+ fakeStatusBarService.showImeSwitcher,
+ )
+ }
+
+ @Test
+ fun start_afterPostInitTaskExecuted_callsDisableFlags_basedOnRegisterBarResult() {
+ initializer.start()
+
+ initController.executePostInitTasks()
+
+ assertThat(commandQueue.disableFlags1ForDisplay(context.displayId))
+ .isEqualTo(fakeStatusBarService.disabledFlags1)
+ assertThat(commandQueue.disableFlags2ForDisplay(context.displayId))
+ .isEqualTo(fakeStatusBarService.disabledFlags2)
+ }
+
+ @Test
+ fun start_beforePostInitTaskExecuted_doesNotCallsDisableFlags() {
+ initializer.start()
+
+ assertThat(commandQueue.disableFlags1ForDisplay(context.displayId)).isNull()
+ assertThat(commandQueue.disableFlags2ForDisplay(context.displayId)).isNull()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/core/StatusBarOrchestratorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/core/StatusBarOrchestratorTest.kt
new file mode 100644
index 000000000000..580336539c37
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/core/StatusBarOrchestratorTest.kt
@@ -0,0 +1,335 @@
+/*
+ * 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.core
+
+import android.platform.test.annotations.EnableFlags
+import android.view.View
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.unconfinedTestDispatcher
+import com.android.systemui.plugins.DarkIconDispatcher
+import com.android.systemui.plugins.mockPluginDependencyProvider
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.power.data.repository.fakePowerRepository
+import com.android.systemui.power.shared.model.WakeSleepReason
+import com.android.systemui.power.shared.model.WakefulnessState
+import com.android.systemui.shade.mockNotificationShadeWindowViewController
+import com.android.systemui.shade.mockShadeSurface
+import com.android.systemui.statusbar.data.model.StatusBarMode
+import com.android.systemui.statusbar.data.model.StatusBarMode.LIGHTS_OUT
+import com.android.systemui.statusbar.data.model.StatusBarMode.LIGHTS_OUT_TRANSPARENT
+import com.android.systemui.statusbar.data.model.StatusBarMode.OPAQUE
+import com.android.systemui.statusbar.data.model.StatusBarMode.TRANSPARENT
+import com.android.systemui.statusbar.data.repository.fakeStatusBarModeRepository
+import com.android.systemui.statusbar.phone.mockPhoneStatusBarTransitions
+import com.android.systemui.statusbar.phone.mockPhoneStatusBarViewController
+import com.android.systemui.statusbar.window.data.model.StatusBarWindowState
+import com.android.systemui.statusbar.window.data.repository.fakeStatusBarWindowStateRepositoryStore
+import com.android.systemui.statusbar.window.data.repository.statusBarWindowStateRepositoryStore
+import com.android.systemui.statusbar.window.fakeStatusBarWindowController
+import com.android.systemui.testKosmos
+import com.android.wm.shell.bubbles.bubbles
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.never
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+
+@EnableFlags(StatusBarSimpleFragment.FLAG_NAME)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class StatusBarOrchestratorTest : SysuiTestCase() {
+
+ private val kosmos =
+ testKosmos().also {
+ it.testDispatcher = it.unconfinedTestDispatcher
+ it.statusBarWindowStateRepositoryStore = it.fakeStatusBarWindowStateRepositoryStore
+ }
+ private val testScope = kosmos.testScope
+ private val statusBarViewController = kosmos.mockPhoneStatusBarViewController
+ private val statusBarWindowController = kosmos.fakeStatusBarWindowController
+ private val statusBarModeRepository = kosmos.fakeStatusBarModeRepository
+ private val pluginDependencyProvider = kosmos.mockPluginDependencyProvider
+ private val notificationShadeWindowViewController =
+ kosmos.mockNotificationShadeWindowViewController
+ private val shadeSurface = kosmos.mockShadeSurface
+ private val bouncerRepository = kosmos.fakeKeyguardBouncerRepository
+ private val fakeStatusBarWindowStateRepositoryStore =
+ kosmos.fakeStatusBarWindowStateRepositoryStore
+ private val fakePowerRepository = kosmos.fakePowerRepository
+ private val mockPhoneStatusBarTransitions = kosmos.mockPhoneStatusBarTransitions
+ private val mockBubbles = kosmos.bubbles
+
+ private val orchestrator = kosmos.statusBarOrchestrator
+
+ @Test
+ fun start_setsUpPluginDependencies() {
+ orchestrator.start()
+
+ verify(pluginDependencyProvider).allowPluginDependency(DarkIconDispatcher::class.java)
+ verify(pluginDependencyProvider).allowPluginDependency(StatusBarStateController::class.java)
+ }
+
+ @Test
+ fun start_attachesWindow() {
+ orchestrator.start()
+
+ assertThat(statusBarWindowController.isAttached).isTrue()
+ }
+
+ @Test
+ fun start_setsStatusBarControllerOnShade() {
+ orchestrator.start()
+
+ verify(notificationShadeWindowViewController)
+ .setStatusBarViewController(statusBarViewController)
+ }
+
+ @Test
+ fun start_updatesShadeExpansion() {
+ orchestrator.start()
+
+ verify(shadeSurface).updateExpansionAndVisibility()
+ }
+
+ @Test
+ fun bouncerShowing_setsImportanceForA11yToNoHideDescendants() =
+ testScope.runTest {
+ orchestrator.start()
+
+ bouncerRepository.setPrimaryShow(isShowing = true)
+
+ verify(statusBarViewController)
+ .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS)
+ }
+
+ @Test
+ fun bouncerNotShowing_setsImportanceForA11yToNoHideDescendants() =
+ testScope.runTest {
+ orchestrator.start()
+
+ bouncerRepository.setPrimaryShow(isShowing = false)
+
+ verify(statusBarViewController)
+ .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_AUTO)
+ }
+
+ @Test
+ fun deviceGoesToSleep_barTransitionsAnimationsAreFinished() =
+ testScope.runTest {
+ putDeviceToSleep()
+
+ orchestrator.start()
+
+ verify(mockPhoneStatusBarTransitions).finishAnimations()
+ }
+
+ @Test
+ fun deviceIsAwake_barTransitionsAnimationsAreNotFinished() =
+ testScope.runTest {
+ awakeDevice()
+
+ orchestrator.start()
+
+ verify(mockPhoneStatusBarTransitions, never()).finishAnimations()
+ }
+
+ @Test
+ fun statusBarVisible_notifiesBubbles() =
+ testScope.runTest {
+ setStatusBarMode(TRANSPARENT)
+ setStatusBarWindowState(StatusBarWindowState.Showing)
+
+ orchestrator.start()
+
+ verify(mockBubbles).onStatusBarVisibilityChanged(/* visible= */ true)
+ }
+
+ @Test
+ fun statusBarInLightsOutMode_notifiesBubblesWithStatusBarInvisible() =
+ testScope.runTest {
+ setStatusBarMode(LIGHTS_OUT)
+ setStatusBarWindowState(StatusBarWindowState.Showing)
+
+ orchestrator.start()
+
+ verify(mockBubbles).onStatusBarVisibilityChanged(/* visible= */ false)
+ }
+
+ @Test
+ fun statusBarInLightsOutTransparentMode_notifiesBubblesWithStatusBarInvisible() =
+ testScope.runTest {
+ setStatusBarMode(LIGHTS_OUT_TRANSPARENT)
+ setStatusBarWindowState(StatusBarWindowState.Showing)
+
+ orchestrator.start()
+
+ verify(mockBubbles).onStatusBarVisibilityChanged(/* visible= */ false)
+ }
+
+ @Test
+ fun statusBarWindowNotShowing_notifiesBubblesWithStatusBarInvisible() =
+ testScope.runTest {
+ setStatusBarMode(TRANSPARENT)
+ setStatusBarWindowState(StatusBarWindowState.Hidden)
+
+ orchestrator.start()
+
+ verify(mockBubbles).onStatusBarVisibilityChanged(/* visible= */ false)
+ }
+
+ @Test
+ fun statusBarModeChange_transitionsToModeWithAnimation() =
+ testScope.runTest {
+ awakeDevice()
+ clearTransientStatusBar()
+ setStatusBarWindowState(StatusBarWindowState.Showing)
+ setStatusBarMode(TRANSPARENT)
+
+ orchestrator.start()
+
+ verify(mockPhoneStatusBarTransitions)
+ .transitionTo(TRANSPARENT.toTransitionModeInt(), /* animate= */ true)
+ }
+
+ @Test
+ fun statusBarModeChange_keepsTransitioningAsModeChanges() =
+ testScope.runTest {
+ awakeDevice()
+ clearTransientStatusBar()
+ setStatusBarWindowState(StatusBarWindowState.Showing)
+ setStatusBarMode(TRANSPARENT)
+
+ orchestrator.start()
+
+ verify(mockPhoneStatusBarTransitions)
+ .transitionTo(TRANSPARENT.toTransitionModeInt(), /* animate= */ true)
+
+ setStatusBarMode(OPAQUE)
+ verify(mockPhoneStatusBarTransitions)
+ .transitionTo(OPAQUE.toTransitionModeInt(), /* animate= */ true)
+
+ setStatusBarMode(LIGHTS_OUT)
+ verify(mockPhoneStatusBarTransitions)
+ .transitionTo(LIGHTS_OUT.toTransitionModeInt(), /* animate= */ true)
+
+ setStatusBarMode(LIGHTS_OUT_TRANSPARENT)
+ verify(mockPhoneStatusBarTransitions)
+ .transitionTo(LIGHTS_OUT_TRANSPARENT.toTransitionModeInt(), /* animate= */ true)
+ }
+
+ @Test
+ fun statusBarModeChange_transientIsShown_transitionsToModeWithoutAnimation() =
+ testScope.runTest {
+ awakeDevice()
+ setTransientStatusBar()
+ setStatusBarWindowState(StatusBarWindowState.Showing)
+ setStatusBarMode(TRANSPARENT)
+
+ orchestrator.start()
+
+ verify(mockPhoneStatusBarTransitions)
+ .transitionTo(/* mode= */ TRANSPARENT.toTransitionModeInt(), /* animate= */ false)
+ }
+
+ @Test
+ fun statusBarModeChange_windowIsHidden_transitionsToModeWithoutAnimation() =
+ testScope.runTest {
+ awakeDevice()
+ clearTransientStatusBar()
+ setStatusBarWindowState(StatusBarWindowState.Hidden)
+ setStatusBarMode(TRANSPARENT)
+
+ orchestrator.start()
+
+ verify(mockPhoneStatusBarTransitions)
+ .transitionTo(/* mode= */ TRANSPARENT.toTransitionModeInt(), /* animate= */ false)
+ }
+
+ @Test
+ fun statusBarModeChange_deviceIsAsleep_transitionsToModeWithoutAnimation() =
+ testScope.runTest {
+ putDeviceToSleep()
+ clearTransientStatusBar()
+ setStatusBarWindowState(StatusBarWindowState.Showing)
+ setStatusBarMode(TRANSPARENT)
+
+ orchestrator.start()
+
+ verify(mockPhoneStatusBarTransitions)
+ .transitionTo(/* mode= */ TRANSPARENT.toTransitionModeInt(), /* animate= */ false)
+ }
+
+ @Test
+ fun statusBarModeAnimationConditionsChange_withoutBarModeChange_noNewTransitionsHappen() =
+ testScope.runTest {
+ awakeDevice()
+ clearTransientStatusBar()
+ setStatusBarWindowState(StatusBarWindowState.Showing)
+ setStatusBarMode(TRANSPARENT)
+
+ orchestrator.start()
+
+ putDeviceToSleep()
+ awakeDevice()
+ setTransientStatusBar()
+ clearTransientStatusBar()
+
+ verify(mockPhoneStatusBarTransitions, times(1))
+ .transitionTo(TRANSPARENT.toTransitionModeInt(), /* animate= */ true)
+ }
+
+ private fun putDeviceToSleep() {
+ fakePowerRepository.updateWakefulness(
+ rawState = WakefulnessState.ASLEEP,
+ lastWakeReason = WakeSleepReason.KEY,
+ lastSleepReason = WakeSleepReason.KEY,
+ powerButtonLaunchGestureTriggered = true,
+ )
+ }
+
+ private fun awakeDevice() {
+ fakePowerRepository.updateWakefulness(
+ rawState = WakefulnessState.AWAKE,
+ lastWakeReason = WakeSleepReason.KEY,
+ lastSleepReason = WakeSleepReason.KEY,
+ powerButtonLaunchGestureTriggered = true,
+ )
+ }
+
+ private fun setTransientStatusBar() {
+ statusBarModeRepository.defaultDisplay.showTransient()
+ }
+
+ private fun clearTransientStatusBar() {
+ statusBarModeRepository.defaultDisplay.clearTransient()
+ }
+
+ private fun setStatusBarWindowState(state: StatusBarWindowState) {
+ fakeStatusBarWindowStateRepositoryStore.defaultDisplay.setWindowState(state)
+ }
+
+ private fun setStatusBarMode(statusBarMode: StatusBarMode) {
+ statusBarModeRepository.defaultDisplay.statusBarMode.value = statusBarMode
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt
index 0407fc14d35a..ac7388281a15 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt
@@ -24,7 +24,7 @@ import androidx.test.filters.SmallTest
import com.android.dx.mockito.inline.extended.ExtendedMockito
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.NOTIFICATIONS_USE_PEOPLE_FILTERING
import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype
+import com.android.systemui.statusbar.notification.shared.NotificationMinimalism
import com.android.systemui.statusbar.notification.shared.PriorityPeopleSection
import com.android.systemui.util.DeviceConfigProxyFake
import com.android.systemui.util.Utils
@@ -42,7 +42,7 @@ import org.mockito.quality.Strictness
@RunWith(AndroidJUnit4::class)
@SmallTest
// this class has no testable logic with either of these flags enabled
-@DisableFlags(PriorityPeopleSection.FLAG_NAME, NotificationMinimalismPrototype.FLAG_NAME)
+@DisableFlags(PriorityPeopleSection.FLAG_NAME, NotificationMinimalism.FLAG_NAME)
class NotificationSectionsFeatureManagerTest : SysuiTestCase() {
lateinit var manager: NotificationSectionsFeatureManager
private val proxyFake = DeviceConfigProxyFake()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinatorTest.kt
index deb3fc1224ce..a3f845225a99 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinatorTest.kt
@@ -15,8 +15,8 @@
*/
package com.android.systemui.statusbar.notification.collection.coordinator
-import android.app.Flags.lifetimeExtensionRefactor
import android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR
+import android.app.Flags.lifetimeExtensionRefactor
import android.app.Notification
import android.app.RemoteInputHistoryItem
import android.os.Handler
@@ -47,10 +47,10 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations.initMocks
@SmallTest
@@ -78,21 +78,20 @@ class RemoteInputCoordinatorTest : SysuiTestCase() {
@Before
fun setUp() {
initMocks(this)
- coordinator = RemoteInputCoordinator(
+ coordinator =
+ RemoteInputCoordinator(
dumpManager,
rebuilder,
remoteInputManager,
mainHandler,
- smartReplyController
- )
+ smartReplyController,
+ )
`when`(pipeline.addNotificationLifetimeExtender(any())).thenAnswer {
(it.arguments[0] as NotifLifetimeExtender).setCallback(lifetimeExtensionCallback)
}
`when`(pipeline.getInternalNotifUpdater(any())).thenReturn(notifUpdater)
coordinator.attach(pipeline)
- listener = withArgCaptor {
- verify(remoteInputManager).setRemoteInputListener(capture())
- }
+ listener = withArgCaptor { verify(remoteInputManager).setRemoteInputListener(capture()) }
entry1 = NotificationEntryBuilder().setId(1).build()
entry2 = NotificationEntryBuilder().setId(2).build()
`when`(rebuilder.rebuildForCanceledSmartReplies(any())).thenReturn(sbn)
@@ -101,13 +100,17 @@ class RemoteInputCoordinatorTest : SysuiTestCase() {
`when`(rebuilder.rebuildWithExistingReplies(any())).thenReturn(sbn)
}
- val remoteInputActiveExtender get() = coordinator.mRemoteInputActiveExtender
- val remoteInputHistoryExtender get() = coordinator.mRemoteInputHistoryExtender
- val smartReplyHistoryExtender get() = coordinator.mSmartReplyHistoryExtender
+ val remoteInputActiveExtender
+ get() = coordinator.mRemoteInputActiveExtender
- val collectionListeners get() = captureMany {
- verify(pipeline, times(1)).addCollectionListener(capture())
- }
+ val remoteInputHistoryExtender
+ get() = coordinator.mRemoteInputHistoryExtender
+
+ val smartReplyHistoryExtender
+ get() = coordinator.mSmartReplyHistoryExtender
+
+ val collectionListeners
+ get() = captureMany { verify(pipeline, times(1)).addCollectionListener(capture()) }
@Test
fun testRemoteInputActive() {
@@ -179,7 +182,8 @@ class RemoteInputCoordinatorTest : SysuiTestCase() {
@EnableFlags(FLAG_LIFETIME_EXTENSION_REFACTOR)
fun testRemoteInputLifetimeExtensionListenerTrigger() {
// Create notification with LIFETIME_EXTENDED_BY_DIRECT_REPLY flag.
- val entry = NotificationEntryBuilder()
+ val entry =
+ NotificationEntryBuilder()
.setId(3)
.setTag("entry")
.setFlag(mContext, Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY, true)
@@ -187,9 +191,7 @@ class RemoteInputCoordinatorTest : SysuiTestCase() {
`when`(remoteInputManager.shouldKeepForRemoteInputHistory(entry)).thenReturn(true)
`when`(remoteInputManager.shouldKeepForSmartReplyHistory(entry)).thenReturn(false)
- collectionListeners.forEach {
- it.onEntryUpdated(entry, true)
- }
+ collectionListeners.forEach { it.onEntryUpdated(entry, true) }
verify(rebuilder, times(1)).rebuildForRemoteInputReply(entry)
}
@@ -198,16 +200,15 @@ class RemoteInputCoordinatorTest : SysuiTestCase() {
@EnableFlags(FLAG_LIFETIME_EXTENSION_REFACTOR)
fun testSmartReplyLifetimeExtensionListenerTrigger() {
// Create notification with LIFETIME_EXTENDED_BY_DIRECT_REPLY flag.
- val entry = NotificationEntryBuilder()
+ val entry =
+ NotificationEntryBuilder()
.setId(3)
.setTag("entry")
.setFlag(mContext, Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY, true)
.build()
`when`(remoteInputManager.shouldKeepForRemoteInputHistory(entry)).thenReturn(false)
`when`(remoteInputManager.shouldKeepForSmartReplyHistory(entry)).thenReturn(true)
- collectionListeners.forEach {
- it.onEntryUpdated(entry, true)
- }
+ collectionListeners.forEach { it.onEntryUpdated(entry, true) }
verify(rebuilder, times(1)).rebuildForCanceledSmartReplies(entry)
verify(smartReplyController, times(1)).stopSending(entry)
@@ -217,25 +218,25 @@ class RemoteInputCoordinatorTest : SysuiTestCase() {
@EnableFlags(FLAG_LIFETIME_EXTENSION_REFACTOR)
fun testRepeatedUpdateTriggersRebuild() {
// Create notification with LIFETIME_EXTENDED_BY_DIRECT_REPLY flag.
- val entry = NotificationEntryBuilder()
+ val entry =
+ NotificationEntryBuilder()
.setId(3)
.setTag("entry")
.setFlag(mContext, Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY, true)
.build()
`when`(remoteInputManager.shouldKeepForRemoteInputHistory(entry)).thenReturn(false)
`when`(remoteInputManager.shouldKeepForSmartReplyHistory(entry)).thenReturn(false)
- collectionListeners.forEach {
- it.onEntryUpdated(entry, true)
- }
+ collectionListeners.forEach { it.onEntryUpdated(entry, true) }
- verify(rebuilder, times(1)).rebuildWithExistingReplies(entry)
+ verify(rebuilder, times(1)).rebuildForRemoteInputReply(entry)
}
@Test
@EnableFlags(FLAG_LIFETIME_EXTENSION_REFACTOR)
fun testLifetimeExtensionListenerClearsRemoteInputs() {
// Create notification with LIFETIME_EXTENDED_BY_DIRECT_REPLY flag.
- val entry = NotificationEntryBuilder()
+ val entry =
+ NotificationEntryBuilder()
.setId(3)
.setTag("entry")
.setFlag(mContext, Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY, false)
@@ -245,9 +246,7 @@ class RemoteInputCoordinatorTest : SysuiTestCase() {
`when`(remoteInputManager.shouldKeepForRemoteInputHistory(entry)).thenReturn(false)
`when`(remoteInputManager.shouldKeepForSmartReplyHistory(entry)).thenReturn(false)
- collectionListeners.forEach {
- it.onEntryUpdated(entry, true)
- }
+ collectionListeners.forEach { it.onEntryUpdated(entry, true) }
assertThat(entry.remoteInputs).isNull()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
index 3c583f26b0df..56b70bde2cca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
@@ -45,7 +45,7 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.MockitoAnnotations.initMocks
import org.mockito.Mockito.`when` as whenever
@@ -110,7 +110,7 @@ class StackCoordinatorTest : SysuiTestCase() {
whenever(section.bucket).thenReturn(BUCKET_ALERTING)
afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
verify(stackController).setNotifStats(NotifStats(1, false, true, false, false))
- verifyZeroInteractions(activeNotificationsInteractor)
+ verifyNoMoreInteractions(activeNotificationsInteractor)
}
@Test
@@ -121,7 +121,7 @@ class StackCoordinatorTest : SysuiTestCase() {
whenever(section.bucket).thenReturn(BUCKET_ALERTING)
afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
verify(stackController).setNotifStats(NotifStats(1, true, false, false, false))
- verifyZeroInteractions(activeNotificationsInteractor)
+ verifyNoMoreInteractions(activeNotificationsInteractor)
}
@Test
@@ -130,7 +130,7 @@ class StackCoordinatorTest : SysuiTestCase() {
whenever(section.bucket).thenReturn(BUCKET_SILENT)
afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
verify(stackController).setNotifStats(NotifStats(1, false, false, false, true))
- verifyZeroInteractions(activeNotificationsInteractor)
+ verifyNoMoreInteractions(activeNotificationsInteractor)
}
@Test
@@ -141,7 +141,7 @@ class StackCoordinatorTest : SysuiTestCase() {
whenever(section.bucket).thenReturn(BUCKET_SILENT)
afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
verify(stackController).setNotifStats(NotifStats(1, false, false, true, false))
- verifyZeroInteractions(activeNotificationsInteractor)
+ verifyNoMoreInteractions(activeNotificationsInteractor)
}
@Test
@@ -151,7 +151,7 @@ class StackCoordinatorTest : SysuiTestCase() {
afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
verify(activeNotificationsInteractor)
.setNotifStats(NotifStats(1, false, true, false, false))
- verifyZeroInteractions(stackController)
+ verifyNoMoreInteractions(stackController)
}
@Test
@@ -166,7 +166,7 @@ class StackCoordinatorTest : SysuiTestCase() {
afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
verify(activeNotificationsInteractor)
.setNotifStats(NotifStats(1, true, false, false, false))
- verifyZeroInteractions(stackController)
+ verifyNoMoreInteractions(stackController)
}
@Test
@@ -176,7 +176,7 @@ class StackCoordinatorTest : SysuiTestCase() {
afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
verify(activeNotificationsInteractor)
.setNotifStats(NotifStats(1, false, false, false, true))
- verifyZeroInteractions(stackController)
+ verifyNoMoreInteractions(stackController)
}
@Test
@@ -191,7 +191,7 @@ class StackCoordinatorTest : SysuiTestCase() {
afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
verify(activeNotificationsInteractor)
.setNotifStats(NotifStats(1, false, false, true, false))
- verifyZeroInteractions(stackController)
+ verifyNoMoreInteractions(stackController)
}
@Test
@@ -204,6 +204,6 @@ class StackCoordinatorTest : SysuiTestCase() {
afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
verify(activeNotificationsInteractor)
.setNotifStats(NotifStats(1, hasNonClearableAlertingNotifs = true, false, false, false))
- verifyZeroInteractions(stackController)
+ verifyNoMoreInteractions(stackController)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
index cea8857c01bf..7d5278ed1601 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt
@@ -332,7 +332,10 @@ class HeadsUpNotificationInteractorTest : SysuiTestCase() {
// WHEN a row is pinned
headsUpRepository.setNotifications(fakeHeadsUpRowRepository("key 0", isPinned = true))
// AND the lock screen is shown
- keyguardTransitionRepository.emitInitialStepsFromOff(to = KeyguardState.LOCKSCREEN)
+ keyguardTransitionRepository.emitInitialStepsFromOff(
+ to = KeyguardState.LOCKSCREEN,
+ testSetup = true,
+ )
assertThat(showHeadsUpStatusBar).isFalse()
}
@@ -345,7 +348,10 @@ class HeadsUpNotificationInteractorTest : SysuiTestCase() {
// WHEN a row is pinned
headsUpRepository.setNotifications(fakeHeadsUpRowRepository("key 0", isPinned = true))
// AND the lock screen is shown
- keyguardTransitionRepository.emitInitialStepsFromOff(to = KeyguardState.LOCKSCREEN)
+ keyguardTransitionRepository.emitInitialStepsFromOff(
+ to = KeyguardState.LOCKSCREEN,
+ testSetup = true,
+ )
// AND bypass is enabled
faceAuthRepository.isBypassEnabled.value = true
@@ -359,7 +365,10 @@ class HeadsUpNotificationInteractorTest : SysuiTestCase() {
// WHEN no pinned rows
// AND the lock screen is shown
- keyguardTransitionRepository.emitInitialStepsFromOff(to = KeyguardState.LOCKSCREEN)
+ keyguardTransitionRepository.emitInitialStepsFromOff(
+ to = KeyguardState.LOCKSCREEN,
+ testSetup = true,
+ )
// AND bypass is enabled
faceAuthRepository.isBypassEnabled.value = true
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
index 83ad18b6468b..46f3a6b66429 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
@@ -38,6 +38,7 @@ import com.android.systemui.shade.shadeTestUtil
import com.android.systemui.shared.settings.data.repository.fakeSecureSettingsRepository
import com.android.systemui.statusbar.notification.collection.render.NotifStats
import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
+import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix
import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
import com.android.systemui.testKosmos
import com.android.systemui.util.ui.isAnimating
@@ -254,6 +255,39 @@ class FooterViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
assertThat(buttonLabel).isEqualTo(R.string.manage_notifications_history_text)
}
+ @EnableFlags(ModesEmptyShadeFix.FLAG_NAME)
+ @Test
+ fun manageButtonOnClick_whenHistoryDisabled() =
+ testScope.runTest {
+ val onClick by collectLastValue(underTest.manageOrHistoryButtonClick)
+ runCurrent()
+
+ // WHEN notification history is disabled
+ fakeSecureSettingsRepository.setInt(Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0)
+
+ // THEN onClick leads to settings page
+ assertThat(onClick?.targetIntent?.action)
+ .isEqualTo(Settings.ACTION_NOTIFICATION_SETTINGS)
+ assertThat(onClick?.backStack).isEmpty()
+ }
+
+ @EnableFlags(ModesEmptyShadeFix.FLAG_NAME)
+ @Test
+ fun historyButtonOnClick_whenHistoryEnabled() =
+ testScope.runTest {
+ val onClick by collectLastValue(underTest.manageOrHistoryButtonClick)
+ runCurrent()
+
+ // WHEN notification history is enabled
+ fakeSecureSettingsRepository.setInt(Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 1)
+
+ // THEN onClick leads to history page
+ assertThat(onClick?.targetIntent?.action)
+ .isEqualTo(Settings.ACTION_NOTIFICATION_HISTORY)
+ assertThat(onClick?.backStack?.map { it.action })
+ .containsExactly(Settings.ACTION_NOTIFICATION_SETTINGS)
+ }
+
@Test
fun manageButtonVisible_whenMessageVisible() =
testScope.runTest {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
index 30a1214d69d0..a099c9dc6442 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
@@ -77,7 +77,7 @@ import org.mockito.kotlin.never
import org.mockito.kotlin.spy
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
-import org.mockito.kotlin.verifyZeroInteractions
+import org.mockito.kotlin.verifyNoMoreInteractions
import org.mockito.kotlin.whenever
@SmallTest
@@ -420,7 +420,7 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
val contentToInflate =
FLAG_CONTENT_VIEW_CONTRACTED or FLAG_CONTENT_VIEW_EXPANDED or FLAG_CONTENT_VIEW_HEADS_UP
inflateAndWait(notificationInflater, contentToInflate, row)
- verifyZeroInteractions(fakeRonViewInflater)
+ verifyNoMoreInteractions(fakeRonViewInflater)
}
@Test
@@ -567,7 +567,7 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
// THEN do not dispose old contracted binder handle and change contracted child
verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel })
- verifyZeroInteractions(oldHandle)
+ verifyNoMoreInteractions(oldHandle)
verify(privateLayout, never()).setContractedChild(any())
}
@@ -590,7 +590,7 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
// THEN do not dispose old expanded binder handle and change expanded child
verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel })
- verifyZeroInteractions(oldHandle)
+ verifyNoMoreInteractions(oldHandle)
verify(privateLayout, never()).setExpandedChild(any())
}
@@ -613,7 +613,7 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
// THEN - do not dispose old heads up binder handle and change heads up child
verify(entry).setContentModel(argThat { richOngoingContentModel === mockRonModel })
- verifyZeroInteractions(oldHandle)
+ verifyNoMoreInteractions(oldHandle)
verify(privateLayout, never()).setHeadsUpChild(any())
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 30556bef6af4..7cd306ead027 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -37,7 +37,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.metrics.LogMaker;
@@ -937,7 +937,7 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
@DisableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
public void sensitiveNotificationProtectionControllerListenerNotRegistered() {
initController(/* viewIsAttached= */ true);
- verifyZeroInteractions(mSensitiveNotificationProtectionController);
+ verifyNoMoreInteractions(mSensitiveNotificationProtectionController);
}
@Test
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 c710c56fd516..15ea811287b8 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
@@ -169,6 +169,7 @@ import com.android.systemui.statusbar.PulseExpansionHandler;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.StatusBarStateControllerImpl;
import com.android.systemui.statusbar.core.StatusBarInitializerImpl;
+import com.android.systemui.statusbar.core.StatusBarOrchestrator;
import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
@@ -346,6 +347,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
@Mock private EmergencyGestureIntentFactory mEmergencyGestureIntentFactory;
@Mock private NotificationSettingsInteractor mNotificationSettingsInteractor;
@Mock private ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
+ @Mock private StatusBarOrchestrator mStatusBarOrchestrator;
private ShadeController mShadeController;
private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
private final FakeGlobalSettings mFakeGlobalSettings = new FakeGlobalSettings();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 73e3bf4af31e..e804b33db1f7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -40,7 +40,7 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.animation.Animator;
@@ -1229,7 +1229,7 @@ public class ScrimControllerTest extends SysuiTestCase {
reset(mScrimBehind);
mScrimController.setExpansionAffectsAlpha(false);
mScrimController.setRawPanelExpansionFraction(0.8f);
- verifyZeroInteractions(mScrimBehind);
+ verifyNoMoreInteractions(mScrimBehind);
assertEquals("Scrim opacity shouldn't change when setExpansionAffectsAlpha "
+ "is false", scrimAlpha, mScrimBehind.getViewAlpha(), 0.01f);
@@ -1441,7 +1441,7 @@ public class ScrimControllerTest extends SysuiTestCase {
public void testDoesNotHoldWakeLock_whenUnlocking() {
mScrimController.legacyTransitionTo(ScrimState.UNLOCKED);
finishAnimationsImmediately();
- verifyZeroInteractions(mWakeLock);
+ verifyNoMoreInteractions(mWakeLock);
}
@Test
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 7a34e94ab362..cace60ce4c0e 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
@@ -35,7 +35,6 @@ import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
@@ -352,7 +351,7 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
eq(entry.getKey()), any(NotificationVisibility.class));
// The content intent should NOT be sent on click.
- verifyZeroInteractions(mContentIntent);
+ verifyNoMoreInteractions(mContentIntent);
// Notification should not be cancelled.
verify(mOnUserInteractionCallback, never())
@@ -385,7 +384,7 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
eq(entry.getKey()), any(NotificationVisibility.class));
// The content intent should NOT be sent on click.
- verifyZeroInteractions(mContentIntent);
+ verifyNoMoreInteractions(mContentIntent);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt
index af4f647923a7..4d293b98c165 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt
@@ -621,7 +621,7 @@ class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() {
val signalStrength by collectLastValue(underTest.signalStrength)
// THEN we have not yet talked to satellite manager, since we are well before MIN_UPTIME
- Mockito.verifyZeroInteractions(satelliteManager)
+ Mockito.verifyNoMoreInteractions(satelliteManager)
// WHEN enough time has passed
systemClock.advanceTime(MIN_UPTIME)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerStartableTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerStartableTest.java
index c4a74635d06e..aca263656dd5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerStartableTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerStartableTest.java
@@ -21,7 +21,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSess
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.os.Handler;
import android.os.Looper;
@@ -108,6 +108,6 @@ public class BatteryControllerStartableTest extends SysuiTestCase {
mBatteryControllerStartable.start();
mExecutor.runAllReady();
- verifyZeroInteractions(mBroadcastDispatcher);
+ verifyNoMoreInteractions(mBroadcastDispatcher);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FlashlightControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FlashlightControllerImplTest.kt
index 80cc6eca8405..902caf3ff651 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FlashlightControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FlashlightControllerImplTest.kt
@@ -41,7 +41,6 @@ import org.mockito.Mockito.eq
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
-import org.mockito.Mockito.verifyZeroInteractions
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@@ -89,7 +88,7 @@ class FlashlightControllerImplTest : SysuiTestCase() {
@Test
fun testNoCameraManagerInteractionDirectlyOnConstructor() {
- verifyZeroInteractions(cameraManager)
+ verifyNoMoreInteractions(cameraManager)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RotationLockControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RotationLockControllerImplTest.java
index ea620a6856f1..3f33d2f89f5e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RotationLockControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RotationLockControllerImplTest.java
@@ -19,7 +19,7 @@ package com.android.systemui.statusbar.policy;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -61,7 +61,7 @@ public class RotationLockControllerImplTest extends SysuiTestCase {
public void whenFlagOff_doesntInteractWithDeviceStateRotationController() {
createRotationLockController(new String[0]);
- verifyZeroInteractions(mDeviceStateRotationLockSettingController);
+ verifyNoMoreInteractions(mDeviceStateRotationLockSettingController);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt
index 2127057522a7..573927552acc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt
@@ -73,7 +73,6 @@ import org.mockito.Mock
import org.mockito.Mockito.mock
import org.mockito.Mockito.times
import org.mockito.Mockito.verifyNoMoreInteractions
-import org.mockito.Mockito.verifyZeroInteractions
import org.mockito.MockitoAnnotations
import org.mockito.MockitoSession
import org.mockito.quality.Strictness
@@ -207,7 +206,7 @@ class SensitiveNotificationProtectionControllerTest : SysuiTestCase() {
mediaProjectionCallback.onStart(mediaProjectionInfo)
controller.registerSensitiveStateListener(listener1)
- verifyZeroInteractions(listener1)
+ verifyNoMoreInteractions(listener1)
mediaProjectionCallback.onStop(mediaProjectionInfo)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
index e396b567ac89..0598b87aec9d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt
@@ -133,7 +133,10 @@ class KeyguardStatusBarViewModelTest(flags: FlagsParameterization) : SysuiTestCa
// WHEN HUN displayed on the bypass lock screen
headsUpRepository.setNotifications(FakeHeadsUpRowRepository("key 0", isPinned = true))
- keyguardTransitionRepository.emitInitialStepsFromOff(KeyguardState.LOCKSCREEN)
+ keyguardTransitionRepository.emitInitialStepsFromOff(
+ KeyguardState.LOCKSCREEN,
+ testSetup = true,
+ )
kosmos.sceneContainerRepository.snapToScene(Scenes.Lockscreen)
faceAuthRepository.isBypassEnabled.value = true
diff --git a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusManagerTest.kt
index 0b3dd660532b..516541dcaf50 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusManagerTest.kt
@@ -46,7 +46,6 @@ import org.mockito.Mock
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.inOrder
import org.mockito.Mockito.verifyNoMoreInteractions
-import org.mockito.Mockito.verifyZeroInteractions
import org.mockito.MockitoAnnotations
import org.mockito.quality.Strictness
@@ -175,7 +174,7 @@ class StylusManagerTest : SysuiTestCase() {
fun startListener_hasStarted_doesNothing() {
stylusManager.startListener()
- verifyZeroInteractions(inputManager)
+ verifyNoMoreInteractions(inputManager)
}
@Test
@@ -193,7 +192,7 @@ class StylusManagerTest : SysuiTestCase() {
stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID)
- verifyZeroInteractions(stylusCallback)
+ verifyNoMoreInteractions(stylusCallback)
}
@Test
@@ -282,7 +281,7 @@ class StylusManagerTest : SysuiTestCase() {
stylusManager.onInputDeviceChanged(STYLUS_DEVICE_ID)
- verifyZeroInteractions(stylusCallback)
+ verifyNoMoreInteractions(stylusCallback)
}
@Test
@@ -356,7 +355,7 @@ class StylusManagerTest : SysuiTestCase() {
stylusManager.onInputDeviceRemoved(STYLUS_DEVICE_ID)
- verifyZeroInteractions(stylusCallback)
+ verifyNoMoreInteractions(stylusCallback)
}
@Test
@@ -565,7 +564,7 @@ class StylusManagerTest : SysuiTestCase() {
stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState)
- verifyZeroInteractions(uiEventLogger)
+ verifyNoMoreInteractions(uiEventLogger)
}
@Test
@@ -574,7 +573,7 @@ class StylusManagerTest : SysuiTestCase() {
stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState)
- verifyZeroInteractions(uiEventLogger)
+ verifyNoMoreInteractions(uiEventLogger)
}
@Test
@@ -630,7 +629,7 @@ class StylusManagerTest : SysuiTestCase() {
fun onBatteryStateChanged_hasNotStarted_doesNothing() {
stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState)
- verifyZeroInteractions(inputManager)
+ verifyNoMoreInteractions(inputManager)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerStartableTest.kt
index a5e52a469f97..9592b280be34 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerStartableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerStartableTest.kt
@@ -33,7 +33,6 @@ import org.mockito.Mockito.mock
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
-import org.mockito.Mockito.verifyZeroInteractions
import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@@ -81,7 +80,7 @@ class StylusUsiPowerStartableTest : SysuiTestCase() {
startable.start()
- verifyZeroInteractions(stylusManager)
+ verifyNoMoreInteractions(stylusManager)
}
@Test
@@ -109,7 +108,7 @@ class StylusUsiPowerStartableTest : SysuiTestCase() {
fun onStylusAdded_external_noop() {
startable.onStylusAdded(EXTERNAL_DEVICE_ID)
- verifyZeroInteractions(stylusUsiPowerUi)
+ verifyNoMoreInteractions(stylusUsiPowerUi)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
index 666bdd6a881a..a3d2695227a7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java
@@ -30,7 +30,7 @@ import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.app.Application;
@@ -437,7 +437,7 @@ public class ToastUITest extends SysuiTestCase {
verify(mToastLogger).logOnSkipToastForInvalidDisplay(PACKAGE_NAME_1, TOKEN_1.toString(),
invalidDisplayId);
- verifyZeroInteractions(mWindowManager);
+ verifyNoMoreInteractions(mWindowManager);
}
private View verifyWmAddViewAndAttachToParent() {
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 0d398348bda2..9cfb0bb3900b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -53,7 +53,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
@@ -2362,7 +2362,7 @@ public class BubblesTest extends SysuiTestCase {
@DisableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
@Test
public void doesNotRegisterSensitiveStateListener() {
- verifyZeroInteractions(mSensitiveNotificationProtectionController);
+ verifyNoMoreInteractions(mSensitiveNotificationProtectionController);
}
@EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/BubblesTestActivity.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTestActivity.java
index ab329c894fb2..ab329c894fb2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/BubblesTestActivity.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTestActivity.java
diff --git a/packages/SystemUI/tests/utils/src/android/internal/statusbar/FakeStatusBarService.kt b/packages/SystemUI/tests/utils/src/android/internal/statusbar/FakeStatusBarService.kt
new file mode 100644
index 000000000000..cc0597bc3853
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/android/internal/statusbar/FakeStatusBarService.kt
@@ -0,0 +1,355 @@
+/*
+ * 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.internal.statusbar
+
+import android.app.Notification
+import android.content.ComponentName
+import android.graphics.Rect
+import android.graphics.drawable.Icon
+import android.hardware.biometrics.IBiometricContextListener
+import android.hardware.biometrics.IBiometricSysuiReceiver
+import android.hardware.biometrics.PromptInfo
+import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback
+import android.media.INearbyMediaDevicesProvider
+import android.media.MediaRoute2Info
+import android.net.Uri
+import android.os.Bundle
+import android.os.IBinder
+import android.os.UserHandle
+import android.util.ArrayMap
+import android.view.KeyEvent
+import com.android.internal.logging.InstanceId
+import com.android.internal.statusbar.IAddTileResultCallback
+import com.android.internal.statusbar.ISessionListener
+import com.android.internal.statusbar.IStatusBar
+import com.android.internal.statusbar.IStatusBarService
+import com.android.internal.statusbar.IUndoMediaTransferCallback
+import com.android.internal.statusbar.LetterboxDetails
+import com.android.internal.statusbar.NotificationVisibility
+import com.android.internal.statusbar.RegisterStatusBarResult
+import com.android.internal.statusbar.StatusBarIcon
+import com.android.internal.view.AppearanceRegion
+import org.mockito.kotlin.mock
+
+class FakeStatusBarService : IStatusBarService.Stub() {
+
+ var registeredStatusBar: IStatusBar? = null
+ private set
+
+ var statusBarIcons =
+ ArrayMap<String, StatusBarIcon>().also {
+ it["slot1"] = mock<StatusBarIcon>()
+ it["slot2"] = mock<StatusBarIcon>()
+ }
+ var disabledFlags1 = 1234567
+ var appearance = 123
+ var appearanceRegions =
+ arrayOf(
+ AppearanceRegion(
+ /* appearance = */ 123,
+ /* bounds = */ Rect(/* left= */ 4, /* top= */ 3, /* right= */ 2, /* bottom= */ 1),
+ ),
+ AppearanceRegion(
+ /* appearance = */ 345,
+ /* bounds = */ Rect(/* left= */ 1, /* top= */ 2, /* right= */ 3, /* bottom= */ 4),
+ ),
+ )
+ var imeWindowVis = 987
+ var imeBackDisposition = 654
+ var showImeSwitcher = true
+ var disabledFlags2 = 7654321
+ var navbarColorManagedByIme = true
+ var behavior = 234
+ var requestedVisibleTypes = 345
+ var packageName = "fake.bar.ser.vice"
+ var transientBarTypes = 0
+ var letterboxDetails =
+ arrayOf(
+ LetterboxDetails(
+ /* letterboxInnerBounds = */ Rect(
+ /* left= */ 5,
+ /* top= */ 6,
+ /* right= */ 7,
+ /* bottom= */ 8,
+ ),
+ /* letterboxFullBounds = */ Rect(
+ /* left= */ 1,
+ /* top= */ 2,
+ /* right= */ 3,
+ /* bottom= */ 4,
+ ),
+ /* appAppearance = */ 123,
+ )
+ )
+
+ override fun expandNotificationsPanel() {}
+
+ override fun collapsePanels() {}
+
+ override fun togglePanel() {}
+
+ override fun disable(what: Int, token: IBinder, pkg: String) {
+ disableForUser(what, token, pkg, userId = 0)
+ }
+
+ override fun disableForUser(what: Int, token: IBinder, pkg: String, userId: Int) {}
+
+ override fun disable2(what: Int, token: IBinder, pkg: String) {
+ disable2ForUser(what, token, pkg, userId = 0)
+ }
+
+ override fun disable2ForUser(what: Int, token: IBinder, pkg: String, userId: Int) {}
+
+ override fun getDisableFlags(token: IBinder, userId: Int): IntArray {
+ return intArrayOf(disabledFlags1, disabledFlags2)
+ }
+
+ override fun setIcon(
+ slot: String,
+ iconPackage: String,
+ iconId: Int,
+ iconLevel: Int,
+ contentDescription: String,
+ ) {}
+
+ override fun setIconVisibility(slot: String, visible: Boolean) {}
+
+ override fun removeIcon(slot: String) {}
+
+ override fun setImeWindowStatus(
+ displayId: Int,
+ vis: Int,
+ backDisposition: Int,
+ showImeSwitcher: Boolean,
+ ) {}
+
+ override fun expandSettingsPanel(subPanel: String) {}
+
+ override fun registerStatusBar(callbacks: IStatusBar): RegisterStatusBarResult {
+ registeredStatusBar = callbacks
+ return RegisterStatusBarResult(
+ statusBarIcons,
+ disabledFlags1,
+ appearance,
+ appearanceRegions,
+ imeWindowVis,
+ imeBackDisposition,
+ showImeSwitcher,
+ disabledFlags2,
+ navbarColorManagedByIme,
+ behavior,
+ requestedVisibleTypes,
+ packageName,
+ transientBarTypes,
+ letterboxDetails,
+ )
+ }
+
+ override fun onPanelRevealed(clearNotificationEffects: Boolean, numItems: Int) {}
+
+ override fun onPanelHidden() {}
+
+ override fun clearNotificationEffects() {}
+
+ override fun onNotificationClick(key: String, nv: NotificationVisibility) {}
+
+ override fun onNotificationActionClick(
+ key: String,
+ actionIndex: Int,
+ action: Notification.Action,
+ nv: NotificationVisibility,
+ generatedByAssistant: Boolean,
+ ) {}
+
+ override fun onNotificationError(
+ pkg: String,
+ tag: String,
+ id: Int,
+ uid: Int,
+ initialPid: Int,
+ message: String,
+ userId: Int,
+ ) {}
+
+ override fun onClearAllNotifications(userId: Int) {}
+
+ override fun onNotificationClear(
+ pkg: String,
+ userId: Int,
+ key: String,
+ dismissalSurface: Int,
+ dismissalSentiment: Int,
+ nv: NotificationVisibility,
+ ) {}
+
+ override fun onNotificationVisibilityChanged(
+ newlyVisibleKeys: Array<NotificationVisibility>,
+ noLongerVisibleKeys: Array<NotificationVisibility>,
+ ) {}
+
+ override fun onNotificationExpansionChanged(
+ key: String,
+ userAction: Boolean,
+ expanded: Boolean,
+ notificationLocation: Int,
+ ) {}
+
+ override fun onNotificationDirectReplied(key: String) {}
+
+ override fun onNotificationSmartSuggestionsAdded(
+ key: String,
+ smartReplyCount: Int,
+ smartActionCount: Int,
+ generatedByAssistant: Boolean,
+ editBeforeSending: Boolean,
+ ) {}
+
+ override fun onNotificationSmartReplySent(
+ key: String,
+ replyIndex: Int,
+ reply: CharSequence,
+ notificationLocation: Int,
+ modifiedBeforeSending: Boolean,
+ ) {}
+
+ override fun onNotificationSettingsViewed(key: String) {}
+
+ override fun onNotificationBubbleChanged(key: String, isBubble: Boolean, flags: Int) {}
+
+ override fun onBubbleMetadataFlagChanged(key: String, flags: Int) {}
+
+ override fun hideCurrentInputMethodForBubbles(displayId: Int) {}
+
+ override fun grantInlineReplyUriPermission(
+ key: String,
+ uri: Uri,
+ user: UserHandle,
+ packageName: String,
+ ) {}
+
+ override fun clearInlineReplyUriPermissions(key: String) {}
+
+ override fun onNotificationFeedbackReceived(key: String, feedback: Bundle) {}
+
+ override fun onGlobalActionsShown() {}
+
+ override fun onGlobalActionsHidden() {}
+
+ override fun shutdown() {}
+
+ override fun reboot(safeMode: Boolean) {}
+
+ override fun restart() {}
+
+ override fun addTile(tile: ComponentName) {}
+
+ override fun remTile(tile: ComponentName) {}
+
+ override fun clickTile(tile: ComponentName) {}
+
+ override fun handleSystemKey(key: KeyEvent) {}
+
+ override fun getLastSystemKey(): Int {
+ return -1
+ }
+
+ override fun showPinningEnterExitToast(entering: Boolean) {}
+
+ override fun showPinningEscapeToast() {}
+
+ override fun showAuthenticationDialog(
+ promptInfo: PromptInfo,
+ sysuiReceiver: IBiometricSysuiReceiver,
+ sensorIds: IntArray,
+ credentialAllowed: Boolean,
+ requireConfirmation: Boolean,
+ userId: Int,
+ operationId: Long,
+ opPackageName: String,
+ requestId: Long,
+ ) {}
+
+ override fun onBiometricAuthenticated(modality: Int) {}
+
+ override fun onBiometricHelp(modality: Int, message: String) {}
+
+ override fun onBiometricError(modality: Int, error: Int, vendorCode: Int) {}
+
+ override fun hideAuthenticationDialog(requestId: Long) {}
+
+ override fun setBiometicContextListener(listener: IBiometricContextListener) {}
+
+ override fun setUdfpsRefreshRateCallback(callback: IUdfpsRefreshRateRequestCallback) {}
+
+ override fun showInattentiveSleepWarning() {}
+
+ override fun dismissInattentiveSleepWarning(animated: Boolean) {}
+
+ override fun startTracing() {}
+
+ override fun stopTracing() {}
+
+ override fun isTracing(): Boolean {
+ return false
+ }
+
+ override fun suppressAmbientDisplay(suppress: Boolean) {}
+
+ override fun requestTileServiceListeningState(componentName: ComponentName, userId: Int) {}
+
+ override fun requestAddTile(
+ componentName: ComponentName,
+ label: CharSequence,
+ icon: Icon,
+ userId: Int,
+ callback: IAddTileResultCallback,
+ ) {}
+
+ override fun cancelRequestAddTile(packageName: String) {}
+
+ override fun setNavBarMode(navBarMode: Int) {}
+
+ override fun getNavBarMode(): Int {
+ return -1
+ }
+
+ override fun registerSessionListener(sessionFlags: Int, listener: ISessionListener) {}
+
+ override fun unregisterSessionListener(sessionFlags: Int, listener: ISessionListener) {}
+
+ override fun onSessionStarted(sessionType: Int, instanceId: InstanceId) {}
+
+ override fun onSessionEnded(sessionType: Int, instanceId: InstanceId) {}
+
+ override fun updateMediaTapToTransferSenderDisplay(
+ displayState: Int,
+ routeInfo: MediaRoute2Info,
+ undoCallback: IUndoMediaTransferCallback,
+ ) {}
+
+ override fun updateMediaTapToTransferReceiverDisplay(
+ displayState: Int,
+ routeInfo: MediaRoute2Info,
+ appIcon: Icon,
+ appName: CharSequence,
+ ) {}
+
+ override fun registerNearbyMediaDevicesProvider(provider: INearbyMediaDevicesProvider) {}
+
+ override fun unregisterNearbyMediaDevicesProvider(provider: INearbyMediaDevicesProvider) {}
+
+ override fun showRearDisplayDialog(currentBaseState: Int) {}
+}
diff --git a/packages/SystemUI/tests/utils/src/android/internal/statusbar/StatusBarServiceKosmos.kt b/packages/SystemUI/tests/utils/src/android/internal/statusbar/StatusBarServiceKosmos.kt
new file mode 100644
index 000000000000..1304161e81e1
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/android/internal/statusbar/StatusBarServiceKosmos.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 android.internal.statusbar
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.fakeStatusBarService by Kosmos.Fixture { FakeStatusBarService() }
+
+var Kosmos.statusBarService by Kosmos.Fixture { fakeStatusBarService }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/TestScopeProvider.kt b/packages/SystemUI/tests/utils/src/com/android/keyguard/TestScopeProvider.kt
index 6c35734c6eb4..6c35734c6eb4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/TestScopeProvider.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/keyguard/TestScopeProvider.kt
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/DemoModeKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/DemoModeKosmos.kt
new file mode 100644
index 000000000000..39384fdec396
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/DemoModeKosmos.kt
@@ -0,0 +1,25 @@
+/*
+ * 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
+
+import com.android.systemui.demomode.DemoModeController
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.mockDemoModeController by Kosmos.Fixture { mock<DemoModeController>() }
+
+var Kosmos.demoModeController by Kosmos.Fixture { mockDemoModeController }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/InitControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/InitControllerKosmos.kt
new file mode 100644
index 000000000000..13169e133c9b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/InitControllerKosmos.kt
@@ -0,0 +1,21 @@
+/*
+ * 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
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.initController by Kosmos.Fixture { InitController() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModelKosmos.kt
new file mode 100644
index 000000000000..1c84133d3821
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModelKosmos.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.communal.ui.viewmodel
+
+import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+
+val Kosmos.communalUserActionsViewModel by Fixture {
+ CommunalUserActionsViewModel(
+ deviceUnlockedInteractor = deviceUnlockedInteractor,
+ shadeInteractor = shadeInteractor,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeCommandQueue.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeCommandQueue.kt
index 3a59f6a8784f..601c14509107 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeCommandQueue.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeCommandQueue.kt
@@ -18,6 +18,8 @@
package com.android.systemui.keyguard.data.repository
import android.content.Context
+import androidx.collection.ArrayMap
+import com.android.internal.statusbar.StatusBarIcon
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.settings.DisplayTracker
import com.android.systemui.statusbar.CommandQueue
@@ -31,6 +33,11 @@ class FakeCommandQueue @Inject constructor() :
CommandQueue(mock(Context::class.java), mock(DisplayTracker::class.java)) {
private val callbacks = mutableListOf<Callbacks>()
+ val icons = ArrayMap<String, StatusBarIcon>()
+
+ private val perDisplayDisableFlags1 = mutableMapOf<Int, Int>()
+ private val perDisplayDisableFlags2 = mutableMapOf<Int, Int>()
+
override fun addCallback(callback: Callbacks) {
callbacks.add(callback)
}
@@ -44,6 +51,23 @@ class FakeCommandQueue @Inject constructor() :
}
fun callbackCount(): Int = callbacks.size
+
+ override fun setIcon(slot: String, icon: StatusBarIcon) {
+ icons[slot] = icon
+ }
+
+ override fun disable(displayId: Int, state1: Int, state2: Int, animate: Boolean) {
+ perDisplayDisableFlags1[displayId] = state1
+ perDisplayDisableFlags2[displayId] = state2
+ }
+
+ override fun disable(displayId: Int, state1: Int, state2: Int) {
+ disable(displayId, state1, state2, /* animate= */ false)
+ }
+
+ fun disableFlags1ForDisplay(displayId: Int) = perDisplayDisableFlags1[displayId]
+
+ fun disableFlags2ForDisplay(displayId: Int) = perDisplayDisableFlags2[displayId]
}
@Module
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
index a73c184a1ba8..4d0e603aadd6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
@@ -48,9 +48,8 @@ import kotlinx.coroutines.test.runCurrent
* with OFF -> GONE. Construct with initInLockscreen = false if your test requires this behavior.
*/
@SysUISingleton
-class FakeKeyguardTransitionRepository(
- private val initInLockscreen: Boolean = true,
-) : KeyguardTransitionRepository {
+class FakeKeyguardTransitionRepository(private val initInLockscreen: Boolean = true) :
+ KeyguardTransitionRepository {
private val _transitions =
MutableSharedFlow<TransitionStep>(replay = 3, onBufferOverflow = BufferOverflow.DROP_OLDEST)
override val transitions: SharedFlow<TransitionStep> = _transitions
@@ -63,7 +62,7 @@ class FakeKeyguardTransitionRepository(
ownerName = "",
from = KeyguardState.OFF,
to = KeyguardState.LOCKSCREEN,
- animator = null
+ animator = null,
)
)
override var currentTransitionInfoInternal = _currentTransitionInfo.asStateFlow()
@@ -71,12 +70,7 @@ class FakeKeyguardTransitionRepository(
init {
// Seed with a FINISHED transition in OFF, same as the real repository.
_transitions.tryEmit(
- TransitionStep(
- KeyguardState.OFF,
- KeyguardState.OFF,
- 1f,
- TransitionState.FINISHED,
- )
+ TransitionStep(KeyguardState.OFF, KeyguardState.OFF, 1f, TransitionState.FINISHED)
)
if (initInLockscreen) {
@@ -173,7 +167,7 @@ class FakeKeyguardTransitionRepository(
transitionState = TransitionState.RUNNING,
from = from,
to = to,
- value = 0.5f
+ value = 0.5f,
)
)
testScheduler.runCurrent()
@@ -184,7 +178,7 @@ class FakeKeyguardTransitionRepository(
transitionState = TransitionState.RUNNING,
from = from,
to = to,
- value = 1f
+ value = 1f,
)
)
testScheduler.runCurrent()
@@ -208,7 +202,7 @@ class FakeKeyguardTransitionRepository(
this.sendTransitionStep(
step = step,
validateStep = validateStep,
- ownerName = step.ownerName
+ ownerName = step.ownerName,
)
}
@@ -240,9 +234,9 @@ class FakeKeyguardTransitionRepository(
to = to,
value = value,
transitionState = transitionState,
- ownerName = ownerName
+ ownerName = ownerName,
),
- validateStep: Boolean = true
+ validateStep: Boolean = true,
) {
if (step.transitionState == TransitionState.STARTED) {
_currentTransitionInfo.value =
@@ -273,7 +267,7 @@ class FakeKeyguardTransitionRepository(
fun sendTransitionStepJava(
coroutineScope: CoroutineScope,
step: TransitionStep,
- validateStep: Boolean = true
+ validateStep: Boolean = true,
): Job {
return coroutineScope.launch {
sendTransitionStep(step = step, validateStep = validateStep)
@@ -283,7 +277,7 @@ class FakeKeyguardTransitionRepository(
suspend fun sendTransitionSteps(
steps: List<TransitionStep>,
testScope: TestScope,
- validateSteps: Boolean = true
+ validateSteps: Boolean = true,
) {
steps.forEach {
sendTransitionStep(step = it, validateStep = validateSteps)
@@ -296,7 +290,7 @@ class FakeKeyguardTransitionRepository(
return if (info.animator == null) UUID.randomUUID() else null
}
- override suspend fun emitInitialStepsFromOff(to: KeyguardState) {
+ override suspend fun emitInitialStepsFromOff(to: KeyguardState, testSetup: Boolean) {
tryEmitInitialStepsFromOff(to)
}
@@ -318,14 +312,14 @@ class FakeKeyguardTransitionRepository(
1f,
TransitionState.FINISHED,
ownerName = "KeyguardTransitionRepository(boot)",
- ),
+ )
)
}
override suspend fun updateTransition(
transitionId: UUID,
@FloatRange(from = 0.0, to = 1.0) value: Float,
- state: TransitionState
+ state: TransitionState,
) = Unit
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt
index e2b283b06562..2f13ba4e4966 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt
@@ -34,11 +34,11 @@ val Kosmos.keyguardDismissActionInteractor by
transitionInteractor = keyguardTransitionInteractor,
dismissInteractor = keyguardDismissInteractor,
applicationScope = testScope.backgroundScope,
- sceneInteractor = { sceneInteractor },
deviceUnlockedInteractor = { deviceUnlockedInteractor },
powerInteractor = powerInteractor,
alternateBouncerInteractor = alternateBouncerInteractor,
shadeInteractor = { shadeInteractor },
keyguardInteractor = { keyguardInteractor },
+ sceneInteractor = { sceneInteractor },
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModelKosmos.kt
index 0c538ff1d6fe..ab7ccb3bc029 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModelKosmos.kt
@@ -18,6 +18,7 @@ package com.android.systemui.keyguard.ui.viewmodel
import android.os.fakeExecutorHandler
import com.android.systemui.keyguard.domain.interactor.keyguardBlueprintInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.kosmos.Kosmos
val Kosmos.keyguardBlueprintViewModel by
@@ -25,5 +26,6 @@ val Kosmos.keyguardBlueprintViewModel by
KeyguardBlueprintViewModel(
fakeExecutorHandler,
keyguardBlueprintInteractor,
+ keyguardTransitionInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
index 38626a5dbac3..3c87106bf5aa 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
@@ -47,6 +47,8 @@ val Kosmos.keyguardRootViewModel by Fixture {
alternateBouncerToGoneTransitionViewModel = alternateBouncerToGoneTransitionViewModel,
alternateBouncerToLockscreenTransitionViewModel =
alternateBouncerToLockscreenTransitionViewModel,
+ alternateBouncerToOccludedTransitionViewModel =
+ alternateBouncerToOccludedTransitionViewModel,
aodToGoneTransitionViewModel = aodToGoneTransitionViewModel,
aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel,
aodToOccludedTransitionViewModel = aodToOccludedTransitionViewModel,
@@ -69,9 +71,12 @@ val Kosmos.keyguardRootViewModel by Fixture {
lockscreenToOccludedTransitionViewModel = lockscreenToOccludedTransitionViewModel,
lockscreenToPrimaryBouncerTransitionViewModel =
lockscreenToPrimaryBouncerTransitionViewModel,
+ occludedToAlternateBouncerTransitionViewModel =
+ occludedToAlternateBouncerTransitionViewModel,
occludedToAodTransitionViewModel = occludedToAodTransitionViewModel,
occludedToDozingTransitionViewModel = occludedToDozingTransitionViewModel,
occludedToLockscreenTransitionViewModel = occludedToLockscreenTransitionViewModel,
+ offToLockscreenTransitionViewModel = offToLockscreenTransitionViewModel,
primaryBouncerToAodTransitionViewModel = primaryBouncerToAodTransitionViewModel,
primaryBouncerToGoneTransitionViewModel = primaryBouncerToGoneTransitionViewModel,
primaryBouncerToLockscreenTransitionViewModel =
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAlternateBouncerTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAlternateBouncerTransitionViewModelKosmos.kt
new file mode 100644
index 000000000000..2acd1b40af3e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAlternateBouncerTransitionViewModelKosmos.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+val Kosmos.occludedToAlternateBouncerTransitionViewModel by Fixture {
+ OccludedToAlternateBouncerTransitionViewModel(animationFlow = keyguardTransitionAnimationFlow)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OffToLockscreenTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OffToLockscreenTransitionViewModelKosmos.kt
new file mode 100644
index 000000000000..5d62a0f4a0cf
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OffToLockscreenTransitionViewModelKosmos.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+val Kosmos.offToLockscreenTransitionViewModel by Fixture {
+ OffToLockscreenTransitionViewModel(animationFlow = keyguardTransitionAnimationFlow)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/navigationbar/NavigationBarControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/navigationbar/NavigationBarControllerKosmos.kt
new file mode 100644
index 000000000000..9e2039eb6b54
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/navigationbar/NavigationBarControllerKosmos.kt
@@ -0,0 +1,24 @@
+/*
+ * 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.navigationbar
+
+import com.android.systemui.kosmos.Kosmos
+import org.mockito.kotlin.mock
+
+val Kosmos.mockNavigationBarController by Kosmos.Fixture { mock<NavigationBarController>() }
+
+var Kosmos.navigationBarController by Kosmos.Fixture { mockNavigationBarController }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/PluginDependencyKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/PluginDependencyKosmos.kt
new file mode 100644
index 000000000000..f1388e9975bf
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/PluginDependencyKosmos.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.plugins
+
+import android.testing.LeakCheck
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.utils.leaks.FakePluginManager
+import org.mockito.Mockito.mock
+import org.mockito.kotlin.mock
+
+val Kosmos.leakCheck by Kosmos.Fixture { LeakCheck() }
+
+val Kosmos.fakePluginManager by Kosmos.Fixture { FakePluginManager(leakCheck) }
+
+var Kosmos.pluginManager by Kosmos.Fixture { fakePluginManager }
+
+val Kosmos.pluginDependencyProvider by Kosmos.Fixture { PluginDependencyProvider { pluginManager } }
+
+val Kosmos.mockPluginDependencyProvider by Kosmos.Fixture { mock<PluginDependencyProvider>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt
index d37d8f39b9ee..dbb3e386cc71 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt
@@ -19,14 +19,15 @@ package com.android.systemui.qs.composefragment.viewmodel
import android.content.res.mainResources
import androidx.lifecycle.LifecycleCoroutineScope
import com.android.systemui.common.ui.domain.interactor.configurationInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.qs.footerActionsController
import com.android.systemui.qs.footerActionsViewModelFactory
+import com.android.systemui.qs.panels.domain.interactor.tileSquishinessInteractor
import com.android.systemui.qs.ui.viewmodel.quickSettingsContainerViewModel
import com.android.systemui.shade.largeScreenHeaderHelper
import com.android.systemui.shade.transition.largeScreenShadeInterpolator
import com.android.systemui.statusbar.disableflags.data.repository.disableFlagsRepository
-import com.android.systemui.statusbar.phone.keyguardBypassController
import com.android.systemui.statusbar.sysuiStatusBarStateController
val Kosmos.qsFragmentComposeViewModelFactory by
@@ -41,11 +42,12 @@ val Kosmos.qsFragmentComposeViewModelFactory by
footerActionsViewModelFactory,
footerActionsController,
sysuiStatusBarStateController,
- keyguardBypassController,
+ deviceEntryInteractor,
disableFlagsRepository,
largeScreenShadeInterpolator,
configurationInteractor,
largeScreenHeaderHelper,
+ tileSquishinessInteractor,
lifecycleScope,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/TileSquishinessRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/TileSquishinessRepositoryKosmos.kt
new file mode 100644
index 000000000000..d9fad32aa924
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/TileSquishinessRepositoryKosmos.kt
@@ -0,0 +1,21 @@
+/*
+ * 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.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.tileSquishinessRepository by Kosmos.Fixture { TileSquishinessRepository() }
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 3f62b4d9f9cb..546129fe340e 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
@@ -20,6 +20,9 @@ import com.android.systemui.kosmos.Kosmos
import com.android.systemui.qs.panels.ui.compose.infinitegrid.InfiniteGridLayout
import com.android.systemui.qs.panels.ui.viewmodel.fixedColumnsSizeViewModel
import com.android.systemui.qs.panels.ui.viewmodel.iconTilesViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.tileSquishinessViewModel
val Kosmos.infiniteGridLayout by
- Kosmos.Fixture { InfiniteGridLayout(iconTilesViewModel, fixedColumnsSizeViewModel) }
+ Kosmos.Fixture {
+ InfiniteGridLayout(iconTilesViewModel, fixedColumnsSizeViewModel, tileSquishinessViewModel)
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/TileSquishinessInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/TileSquishinessInteractorKosmos.kt
new file mode 100644
index 000000000000..23db70fad3a9
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/TileSquishinessInteractorKosmos.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.qs.panels.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.panels.data.repository.tileSquishinessRepository
+
+val Kosmos.tileSquishinessInteractor by
+ Kosmos.Fixture { TileSquishinessInteractor(tileSquishinessRepository) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelKosmos.kt
index 40d26242e36c..babbd50ece98 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelKosmos.kt
@@ -27,6 +27,7 @@ val Kosmos.quickQuickSettingsViewModel by
currentTilesInteractor,
fixedColumnsSizeViewModel,
quickQuickSettingsRowInteractor,
+ tileSquishinessViewModel,
iconTilesViewModel,
applicationCoroutineScope,
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileSquishinessViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileSquishinessViewModelKosmos.kt
new file mode 100644
index 000000000000..ecc8cd179a9a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileSquishinessViewModelKosmos.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.qs.panels.ui.viewmodel
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.panels.domain.interactor.tileSquishinessInteractor
+
+val Kosmos.tileSquishinessViewModel by
+ Kosmos.Fixture { TileSquishinessViewModel(tileSquishinessInteractor) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelKosmos.kt
index a80a4095a264..6540ed6bba45 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelKosmos.kt
@@ -17,6 +17,7 @@
package com.android.systemui.qs.ui.viewmodel
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.ui.viewmodel.shadeHeaderViewModelFactory
@@ -24,6 +25,7 @@ val Kosmos.quickSettingsShadeOverlayContentViewModel: QuickSettingsShadeOverlayC
Kosmos.Fixture {
QuickSettingsShadeOverlayContentViewModel(
shadeInteractor = shadeInteractor,
+ sceneInteractor = sceneInteractor,
shadeHeaderViewModelFactory = shadeHeaderViewModelFactory,
quickSettingsContainerViewModel = quickSettingsContainerViewModel,
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
index 737aaf22b557..f842db4c0026 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
@@ -1,7 +1,9 @@
package com.android.systemui.scene
+import android.view.View
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.classifier.domain.interactor.falsingInteractor
+import com.android.systemui.haptics.msdl.msdlPlayer
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.power.domain.interactor.powerInteractor
@@ -13,11 +15,13 @@ import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.FakeOverlay
import com.android.systemui.scene.ui.viewmodel.SceneContainerGestureFilter
+import com.android.systemui.scene.ui.viewmodel.SceneContainerHapticsViewModel
import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
import com.android.systemui.scene.ui.viewmodel.splitEdgeDetector
import com.android.systemui.settings.displayTracker
import com.android.systemui.shade.domain.interactor.shadeInteractor
import kotlinx.coroutines.flow.MutableStateFlow
+import org.mockito.kotlin.mock
var Kosmos.sceneKeys by Fixture {
listOf(
@@ -68,18 +72,32 @@ val Kosmos.transitionState by Fixture {
}
val Kosmos.sceneContainerViewModel by Fixture {
- SceneContainerViewModel(
- sceneInteractor = sceneInteractor,
- falsingInteractor = falsingInteractor,
- powerInteractor = powerInteractor,
- shadeInteractor = shadeInteractor,
- splitEdgeDetector = splitEdgeDetector,
- gestureFilterFactory = sceneContainerGestureFilterFactory,
- displayId = displayTracker.defaultDisplayId,
- motionEventHandlerReceiver = {},
- logger = sceneLogger,
- )
- .apply { setTransitionState(transitionState) }
+ sceneContainerViewModelFactory.create(mock<View>(), displayTracker.defaultDisplayId, {}).apply {
+ setTransitionState(transitionState)
+ }
+}
+
+val Kosmos.sceneContainerViewModelFactory by Fixture {
+ object : SceneContainerViewModel.Factory {
+ override fun create(
+ view: View,
+ displayId: Int,
+ motionEventHandlerReceiver: (SceneContainerViewModel.MotionEventHandler?) -> Unit,
+ ): SceneContainerViewModel =
+ SceneContainerViewModel(
+ sceneInteractor = sceneInteractor,
+ falsingInteractor = falsingInteractor,
+ powerInteractor = powerInteractor,
+ shadeInteractor = shadeInteractor,
+ splitEdgeDetector = splitEdgeDetector,
+ logger = sceneLogger,
+ gestureFilterFactory = sceneContainerGestureFilterFactory,
+ hapticsViewModelFactory = sceneContainerHapticsViewModelFactory,
+ view = view,
+ displayId = displayId,
+ motionEventHandlerReceiver = motionEventHandlerReceiver,
+ )
+ }
}
val Kosmos.sceneContainerGestureFilterFactory by Fixture {
@@ -92,3 +110,16 @@ val Kosmos.sceneContainerGestureFilterFactory by Fixture {
}
}
}
+
+val Kosmos.sceneContainerHapticsViewModelFactory by Fixture {
+ object : SceneContainerHapticsViewModel.Factory {
+ override fun create(view: View): SceneContainerHapticsViewModel {
+ return SceneContainerHapticsViewModel(
+ view = view,
+ sceneInteractor = sceneInteractor,
+ shadeInteractor = shadeInteractor,
+ msdlPlayer = msdlPlayer,
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeViewControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeViewControllerKosmos.kt
index 1ceab68604f3..a9f9c82be98b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeViewControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeViewControllerKosmos.kt
@@ -20,3 +20,13 @@ import com.android.systemui.kosmos.Kosmos
import com.android.systemui.util.mockito.mock
var Kosmos.shadeViewController by Kosmos.Fixture { mock<ShadeViewController>() }
+
+val Kosmos.mockNotificationShadeWindowViewController by
+ Kosmos.Fixture { mock<NotificationShadeWindowViewController>() }
+
+var Kosmos.notificationShadeWindowViewController by
+ Kosmos.Fixture { mockNotificationShadeWindowViewController }
+
+val Kosmos.mockShadeSurface by Kosmos.Fixture { mock<ShadeSurface>() }
+
+var Kosmos.shadeSurface by Kosmos.Fixture { mockShadeSurface }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/LinearLargeScreenShadeInterpolator.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/transition/LinearLargeScreenShadeInterpolator.kt
index d24bcdc834a7..d24bcdc834a7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/LinearLargeScreenShadeInterpolator.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/transition/LinearLargeScreenShadeInterpolator.kt
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeOverlayContentViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeOverlayContentViewModelKosmos.kt
index 7a15fdf95734..718347fc3490 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeOverlayContentViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeOverlayContentViewModelKosmos.kt
@@ -19,6 +19,7 @@ package com.android.systemui.shade.ui.viewmodel
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeOverlayContentViewModel
+import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModelFactory
@@ -27,6 +28,7 @@ val Kosmos.notificationsShadeOverlayContentViewModel:
NotificationsShadeOverlayContentViewModel(
shadeHeaderViewModelFactory = shadeHeaderViewModelFactory,
notificationsPlaceholderViewModelFactory = notificationsPlaceholderViewModelFactory,
+ sceneInteractor = sceneInteractor,
shadeInteractor = shadeInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModelKosmos.kt
index 48c5121c71c1..0aeea4e1a2e5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModelKosmos.kt
@@ -19,11 +19,13 @@ package com.android.systemui.shade.ui.viewmodel
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.qs.ui.adapter.qsSceneAdapter
+import com.android.systemui.scene.domain.interactor.sceneBackInteractor
import com.android.systemui.shade.domain.interactor.shadeInteractor
val Kosmos.shadeUserActionsViewModel: ShadeUserActionsViewModel by Fixture {
ShadeUserActionsViewModel(
qsSceneAdapter = qsSceneAdapter,
shadeInteractor = shadeInteractor,
+ sceneBackInteractor = sceneBackInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/CommandQueueKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/CommandQueueKosmos.kt
index 27f7f6823cc7..f571c1be9e6e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/CommandQueueKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/CommandQueueKosmos.kt
@@ -19,4 +19,10 @@ package com.android.systemui.statusbar
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.util.mockito.mock
-var Kosmos.commandQueue by Kosmos.Fixture { mock<CommandQueue>() }
+val Kosmos.mockCommandQueue by Kosmos.Fixture { mock<CommandQueue>() }
+
+var Kosmos.commandQueue by Kosmos.Fixture { mockCommandQueue }
+
+val Kosmos.mockCommandQueueCallbacks by Kosmos.Fixture { mock<CommandQueue.Callbacks>() }
+
+var Kosmos.commandQueueCallbacks by Kosmos.Fixture { mockCommandQueue }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryHelper.java b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/NotificationEntryHelper.java
index 2420e573421a..2420e573421a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryHelper.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/NotificationEntryHelper.java
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/NotificationRemoteInputManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/NotificationRemoteInputManagerKosmos.kt
index 554bdbe0c382..d436cd4f2ed2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/NotificationRemoteInputManagerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/NotificationRemoteInputManagerKosmos.kt
@@ -19,5 +19,7 @@ package com.android.systemui.statusbar
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.util.mockito.mock
-var Kosmos.notificationRemoteInputManager by
+val Kosmos.mockNotificationRemoteInputManager by
Kosmos.Fixture { mock<NotificationRemoteInputManager>() }
+
+var Kosmos.notificationRemoteInputManager by Kosmos.Fixture { mockNotificationRemoteInputManager }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/CommandQueueInitializerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/CommandQueueInitializerKosmos.kt
new file mode 100644
index 000000000000..cba4e8efe9fe
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/CommandQueueInitializerKosmos.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.core
+
+import android.content.testableContext
+import android.internal.statusbar.fakeStatusBarService
+import com.android.systemui.initController
+import com.android.systemui.keyguard.data.repository.fakeCommandQueue
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.navigationbar.mockNavigationBarController
+import com.android.systemui.statusbar.data.repository.fakeStatusBarModeRepository
+import com.android.systemui.statusbar.mockCommandQueueCallbacks
+
+var Kosmos.commandQueueInitializer by
+ Kosmos.Fixture {
+ CommandQueueInitializer(
+ testableContext,
+ fakeCommandQueue,
+ { mockCommandQueueCallbacks },
+ fakeStatusBarModeRepository,
+ initController,
+ fakeStatusBarService,
+ mockNavigationBarController,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarInitializer.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarInitializer.kt
new file mode 100644
index 000000000000..edd660490e4d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarInitializer.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.core
+
+import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewUpdatedListener
+import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions
+import com.android.systemui.statusbar.phone.PhoneStatusBarViewController
+
+class FakeStatusBarInitializer(
+ private val statusBarViewController: PhoneStatusBarViewController,
+ private val statusBarTransitions: PhoneStatusBarTransitions,
+) : StatusBarInitializer {
+
+ override var statusBarViewUpdatedListener: OnStatusBarViewUpdatedListener? = null
+ set(value) {
+ field = value
+ value?.onStatusBarViewUpdated(statusBarViewController, statusBarTransitions)
+ }
+
+ override fun initializeStatusBar() {}
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarInitializerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarInitializerKosmos.kt
new file mode 100644
index 000000000000..d10320004454
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarInitializerKosmos.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.core
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.phone.phoneStatusBarTransitions
+import com.android.systemui.statusbar.phone.phoneStatusBarViewController
+
+val Kosmos.fakeStatusBarInitializer by
+ Kosmos.Fixture {
+ FakeStatusBarInitializer(phoneStatusBarViewController, phoneStatusBarTransitions)
+ }
+
+var Kosmos.statusBarInitializer by Kosmos.Fixture { fakeStatusBarInitializer }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt
new file mode 100644
index 000000000000..c53e44d514f7
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt
@@ -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 com.android.systemui.statusbar.core
+
+import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.mockDemoModeController
+import com.android.systemui.plugins.mockPluginDependencyProvider
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.shade.mockNotificationShadeWindowViewController
+import com.android.systemui.shade.mockShadeSurface
+import com.android.systemui.statusbar.data.repository.fakeStatusBarModeRepository
+import com.android.systemui.statusbar.mockNotificationRemoteInputManager
+import com.android.systemui.statusbar.phone.mockAutoHideController
+import com.android.systemui.statusbar.window.data.repository.statusBarWindowStateRepositoryStore
+import com.android.systemui.statusbar.window.fakeStatusBarWindowController
+import com.android.wm.shell.bubbles.bubblesOptional
+
+val Kosmos.statusBarOrchestrator by
+ Kosmos.Fixture {
+ StatusBarOrchestrator(
+ applicationCoroutineScope,
+ fakeStatusBarInitializer,
+ fakeStatusBarWindowController,
+ fakeStatusBarModeRepository,
+ mockDemoModeController,
+ mockPluginDependencyProvider,
+ mockAutoHideController,
+ mockNotificationRemoteInputManager,
+ { mockNotificationShadeWindowViewController },
+ mockShadeSurface,
+ bubblesOptional,
+ statusBarWindowStateRepositoryStore,
+ powerInteractor,
+ primaryBouncerInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorKosmos.kt
index b19e221d099c..3d2bd6cf49d4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorKosmos.kt
@@ -45,3 +45,17 @@ var Kosmos.lockScreenShowOnlyUnseenNotificationsSetting: Boolean
UserHandle.USER_CURRENT,
)
}
+
+var Kosmos.lockScreenNotificationMinimalismSetting: Boolean
+ get() =
+ fakeSettings.getIntForUser(
+ Settings.Secure.LOCK_SCREEN_NOTIFICATION_MINIMALISM,
+ UserHandle.USER_CURRENT,
+ ) == 1
+ set(value) {
+ fakeSettings.putIntForUser(
+ Settings.Secure.LOCK_SCREEN_NOTIFICATION_MINIMALISM,
+ if (value) 1 else 0,
+ UserHandle.USER_CURRENT,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
index a9e117affefb..237f7e4c4dc8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
@@ -42,6 +42,7 @@ import com.android.systemui.keyguard.ui.viewmodel.lockscreenToPrimaryBouncerTran
import com.android.systemui.keyguard.ui.viewmodel.occludedToAodTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.occludedToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.occludedToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.offToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.primaryBouncerToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.primaryBouncerToLockscreenTransitionViewModel
import com.android.systemui.kosmos.Kosmos
@@ -85,6 +86,7 @@ val Kosmos.sharedNotificationContainerViewModel by Fixture {
occludedToAodTransitionViewModel = occludedToAodTransitionViewModel,
occludedToGoneTransitionViewModel = occludedToGoneTransitionViewModel,
occludedToLockscreenTransitionViewModel = occludedToLockscreenTransitionViewModel,
+ offToLockscreenTransitionViewModel = offToLockscreenTransitionViewModel,
primaryBouncerToGoneTransitionViewModel = primaryBouncerToGoneTransitionViewModel,
primaryBouncerToLockscreenTransitionViewModel =
primaryBouncerToLockscreenTransitionViewModel,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/AutoHideKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/AutoHideKosmos.kt
new file mode 100644
index 000000000000..090ce31bd43c
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/AutoHideKosmos.kt
@@ -0,0 +1,24 @@
+/*
+ * 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.phone
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.mockAutoHideController by Kosmos.Fixture { mock<AutoHideController>() }
+
+var Kosmos.autoHideController by Kosmos.Fixture { mockAutoHideController }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/PhoneStatusBarKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/PhoneStatusBarKosmos.kt
new file mode 100644
index 000000000000..603ee08b6b28
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/PhoneStatusBarKosmos.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.statusbar.phone
+
+import com.android.systemui.kosmos.Kosmos
+import org.mockito.Mockito.mock
+
+val Kosmos.mockPhoneStatusBarViewController: PhoneStatusBarViewController by
+ Kosmos.Fixture { mock(PhoneStatusBarViewController::class.java) }
+
+var Kosmos.phoneStatusBarViewController by Kosmos.Fixture { mockPhoneStatusBarViewController }
+
+val Kosmos.mockPhoneStatusBarTransitions: PhoneStatusBarTransitions by
+ Kosmos.Fixture { mock(PhoneStatusBarTransitions::class.java) }
+
+var Kosmos.phoneStatusBarTransitions by Kosmos.Fixture { mockPhoneStatusBarTransitions }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileTelephonyHelpers.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileTelephonyHelpers.kt
index ec04da7030b7..ec04da7030b7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileTelephonyHelpers.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileTelephonyHelpers.kt
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowController.kt
new file mode 100644
index 000000000000..528c9d9ec64d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowController.kt
@@ -0,0 +1,54 @@
+/*
+ * 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.window
+
+import android.view.View
+import android.view.ViewGroup
+import com.android.systemui.animation.ActivityTransitionAnimator
+import com.android.systemui.fragments.FragmentHostManager
+import java.util.Optional
+
+class FakeStatusBarWindowController : StatusBarWindowController {
+
+ var isAttached = false
+ private set
+
+ override val statusBarHeight: Int = 0
+
+ override fun refreshStatusBarHeight() {}
+
+ override fun attach() {
+ isAttached = true
+ }
+
+ override fun addViewToWindow(view: View, layoutParams: ViewGroup.LayoutParams) {}
+
+ override val backgroundView: View
+ get() = throw NotImplementedError()
+
+ override val fragmentHostManager: FragmentHostManager
+ get() = throw NotImplementedError()
+
+ override fun wrapAnimationControllerIfInStatusBar(
+ rootView: View,
+ animationController: ActivityTransitionAnimator.Controller,
+ ): Optional<ActivityTransitionAnimator.Controller> = Optional.empty()
+
+ override fun setForceStatusBarVisible(forceStatusBarVisible: Boolean) {}
+
+ override fun setOngoingProcessRequiresStatusBarVisible(visible: Boolean) {}
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/StatusBarWindowControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/StatusBarWindowControllerKosmos.kt
new file mode 100644
index 000000000000..c198b35be289
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/StatusBarWindowControllerKosmos.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.window
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.fakeStatusBarWindowController by Kosmos.Fixture { FakeStatusBarWindowController() }
+
+var Kosmos.statusBarWindowController by Kosmos.Fixture { fakeStatusBarWindowController }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/data/repository/FakeStatusBarWindowStatePerDisplayRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/data/repository/FakeStatusBarWindowStatePerDisplayRepository.kt
new file mode 100644
index 000000000000..6532a7ecc85a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/data/repository/FakeStatusBarWindowStatePerDisplayRepository.kt
@@ -0,0 +1,46 @@
+/*
+ * 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.window.data.repository
+
+import android.view.Display
+import com.android.systemui.statusbar.window.data.model.StatusBarWindowState
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeStatusBarWindowStateRepositoryStore : StatusBarWindowStateRepositoryStore {
+
+ private val perDisplayRepos = mutableMapOf<Int, FakeStatusBarWindowStatePerDisplayRepository>()
+
+ override val defaultDisplay: FakeStatusBarWindowStatePerDisplayRepository =
+ forDisplay(Display.DEFAULT_DISPLAY)
+
+ override fun forDisplay(displayId: Int): FakeStatusBarWindowStatePerDisplayRepository =
+ perDisplayRepos.computeIfAbsent(displayId) {
+ FakeStatusBarWindowStatePerDisplayRepository()
+ }
+}
+
+class FakeStatusBarWindowStatePerDisplayRepository : StatusBarWindowStatePerDisplayRepository {
+
+ private val _windowState = MutableStateFlow(StatusBarWindowState.Hidden)
+
+ override val windowState = _windowState.asStateFlow()
+
+ fun setWindowState(state: StatusBarWindowState) {
+ _windowState.value = state
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/data/repository/StatusBarWindowStateRepositoryStoreKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/data/repository/StatusBarWindowStateRepositoryStoreKosmos.kt
index e2b7f5fa0717..2205a3b6e084 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/data/repository/StatusBarWindowStateRepositoryStoreKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/data/repository/StatusBarWindowStateRepositoryStoreKosmos.kt
@@ -21,6 +21,9 @@ import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.settings.displayTracker
import com.android.systemui.statusbar.commandQueue
+val Kosmos.fakeStatusBarWindowStateRepositoryStore by
+ Kosmos.Fixture { FakeStatusBarWindowStateRepositoryStore() }
+
class KosmosStatusBarWindowStatePerDisplayRepositoryFactory(private val kosmos: Kosmos) :
StatusBarWindowStatePerDisplayRepositoryFactory {
override fun create(displayId: Int): StatusBarWindowStatePerDisplayRepositoryImpl {
@@ -32,7 +35,7 @@ class KosmosStatusBarWindowStatePerDisplayRepositoryFactory(private val kosmos:
}
}
-val Kosmos.statusBarWindowStateRepositoryStore by
+var Kosmos.statusBarWindowStateRepositoryStore: StatusBarWindowStateRepositoryStore by
Kosmos.Fixture {
StatusBarWindowStateRepositoryStoreImpl(
displayId = displayTracker.defaultDisplayId,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/stylus/FixedCapacityBatteryState.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/stylus/FixedCapacityBatteryState.kt
index 7e010886c394..7e010886c394 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/stylus/FixedCapacityBatteryState.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/stylus/FixedCapacityBatteryState.kt
diff --git a/packages/VpnDialogs/res/values-kn/strings.xml b/packages/VpnDialogs/res/values-kn/strings.xml
index 4f8d90b20405..f268af4ffdeb 100644
--- a/packages/VpnDialogs/res/values-kn/strings.xml
+++ b/packages/VpnDialogs/res/values-kn/strings.xml
@@ -32,7 +32,7 @@
<string name="always_on_disconnected_message_settings_link" msgid="6172280302829992412">"VPN ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ಬದಲಾಯಿಸಿ"</string>
<string name="configure" msgid="4905518375574791375">"ಕಾನ್ಫಿಗರ್ ಮಾಡು"</string>
<string name="disconnect" msgid="971412338304200056">"ಸಂಪರ್ಕ ಕಡಿತಗೊಳಿಸು"</string>
- <string name="open_app" msgid="3717639178595958667">"ಅಪ್ಲಿಕೇಶನ್ ತೆರೆಯಿರಿ"</string>
+ <string name="open_app" msgid="3717639178595958667">"ಆ್ಯಪ್ ತೆರೆಯಿರಿ"</string>
<string name="dismiss" msgid="6192859333764711227">"ವಜಾಗೊಳಿಸಿ"</string>
<string name="sanitized_vpn_label_with_ellipsis" msgid="7014327474633422235">"<xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_0">%1$s</xliff:g>… ( <xliff:g id="SANITIZED_VPN_LABEL_WITH_ELLIPSIS_1">%2$s</xliff:g>)"</string>
<string name="sanitized_vpn_label" msgid="1877415015009794766">"<xliff:g id="SANITIZED_VPN_LABEL_0">%1$s</xliff:g> ( <xliff:g id="SANITIZED_VPN_LABEL_1">%2$s</xliff:g>)"</string>
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java
index 428eb57f20bf..b4b871524291 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java
@@ -84,10 +84,10 @@ public class RavenwoodTestStats {
try {
mOutputWriter = new PrintWriter(mOutputFile);
} catch (IOException e) {
- throw new RuntimeException("Failed to crete logfile. File=" + mOutputFile, e);
+ throw new RuntimeException("Failed to create logfile. File=" + mOutputFile, e);
}
- // Crete the "latest" symlink.
+ // Create the "latest" symlink.
Path symlink = Paths.get(tmpdir, basename + "latest.csv");
try {
if (Files.exists(symlink)) {
@@ -96,7 +96,7 @@ public class RavenwoodTestStats {
Files.createSymbolicLink(symlink, Paths.get(mOutputFile.getName()));
} catch (IOException e) {
- throw new RuntimeException("Failed to crete logfile. File=" + mOutputFile, e);
+ throw new RuntimeException("Failed to create logfile. File=" + mOutputFile, e);
}
Log.i(TAG, "Test result stats file: " + mOutputFile);
diff --git a/ravenwood/runtime-jni/ravenwood_sysprop.cpp b/ravenwood/runtime-jni/ravenwood_sysprop.cpp
index 4fb61b6c590d..aafc4268d782 100644
--- a/ravenwood/runtime-jni/ravenwood_sysprop.cpp
+++ b/ravenwood/runtime-jni/ravenwood_sysprop.cpp
@@ -56,7 +56,7 @@ static bool property_set(const char* key, const char* value) {
if (key == nullptr || *key == '\0') return false;
if (value == nullptr) value = "";
bool read_only = !strncmp(key, "ro.", 3);
- if (!read_only && strlen(value) >= PROP_VALUE_MAX) return -1;
+ if (!read_only && strlen(value) >= PROP_VALUE_MAX) return false;
std::lock_guard lock(g_properties_lock);
auto [it, success] = g_properties.emplace(key, value);
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 3224b27d5803..73b7b35ba9a7 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -165,16 +165,27 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
protected final AccessibilitySecurityPolicy mSecurityPolicy;
protected final AccessibilityTrace mTrace;
- // The attribution tag set by the service that is bound to this instance
+ /** The attribution tag set by the client that is bound to this instance */
protected String mAttributionTag;
protected int mDisplayTypes = DISPLAY_TYPE_DEFAULT;
- // The service that's bound to this instance. Whenever this value is non-null, this
- // object is registered as a death recipient
- IBinder mService;
+ /**
+ * Binder of the {@link #mClient}.
+ *
+ * <p>Whenever this value is non-null, it should be registered as a {@link
+ * IBinder.DeathRecipient}
+ */
+ @Nullable IBinder mClientBinder;
- IAccessibilityServiceClient mServiceInterface;
+ /**
+ * The accessibility client this class represents.
+ *
+ * <p>The client is in the application process, i.e., it's a client of system_server. Depending
+ * on the use case, the client can be an {@link AccessibilityService}, a {@code UiAutomation},
+ * etc.
+ */
+ @Nullable IAccessibilityServiceClient mClient;
int mEventTypes;
@@ -218,10 +229,10 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
int mGenericMotionEventSources;
int mObservedMotionEventSources;
- // the events pending events to be dispatched to this service
+ /** Pending events to be dispatched to the client */
final SparseArray<AccessibilityEvent> mPendingEvents = new SparseArray<>();
- /** Whether this service relies on its {@link AccessibilityCache} being up to date */
+ /** Whether the client relies on its {@link AccessibilityCache} being up to date */
boolean mUsesAccessibilityCache = false;
// Handler only for dispatching accessibility events since we use event
@@ -230,7 +241,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
final SparseArray<IBinder> mOverlayWindowTokens = new SparseArray();
- // All the embedded accessibility overlays that have been added by this service.
+ /** All the embedded accessibility overlays that have been added by the client. */
private List<SurfaceControl> mOverlays = new ArrayList<>();
/** The timestamp of requesting to take screenshot in milliseconds */
@@ -274,7 +285,8 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
/**
* Called back to notify system that the client has changed
- * @param serviceInfoChanged True if the service's AccessibilityServiceInfo changed.
+ *
+ * @param serviceInfoChanged True if the client's AccessibilityServiceInfo changed.
*/
void onClientChangeLocked(boolean serviceInfoChanged);
@@ -360,21 +372,22 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
mIPlatformCompat = IPlatformCompat.Stub.asInterface(
ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
- mEventDispatchHandler = new Handler(mainHandler.getLooper()) {
- @Override
- public void handleMessage(Message message) {
- final int eventType = message.what;
- AccessibilityEvent event = (AccessibilityEvent) message.obj;
- boolean serviceWantsEvent = message.arg1 != 0;
- notifyAccessibilityEventInternal(eventType, event, serviceWantsEvent);
- }
- };
+ mEventDispatchHandler =
+ new Handler(mainHandler.getLooper()) {
+ @Override
+ public void handleMessage(Message message) {
+ final int eventType = message.what;
+ AccessibilityEvent event = (AccessibilityEvent) message.obj;
+ boolean clientWantsEvent = message.arg1 != 0;
+ notifyAccessibilityEventInternal(eventType, event, clientWantsEvent);
+ }
+ };
setDynamicallyConfigurableProperties(accessibilityServiceInfo);
}
@Override
public boolean onKeyEvent(KeyEvent keyEvent, int sequenceNumber) {
- if (!mRequestFilterKeyEvents || (mServiceInterface == null)) {
+ if (!mRequestFilterKeyEvents || (mClient == null)) {
return false;
}
if((mAccessibilityServiceInfo.getCapabilities()
@@ -388,7 +401,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
if (svcClientTracingEnabled()) {
logTraceSvcClient("onKeyEvent", keyEvent + ", " + sequenceNumber);
}
- mServiceInterface.onKeyEvent(keyEvent, sequenceNumber);
+ mClient.onKeyEvent(keyEvent, sequenceNumber);
} catch (RemoteException e) {
return false;
}
@@ -470,7 +483,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
}
public boolean canReceiveEventsLocked() {
- return (mEventTypes != 0 && mService != null);
+ return (mEventTypes != 0 && mClientBinder != null);
}
@RequiresNoPermission
@@ -520,7 +533,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
final long identity = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- // If the XML manifest had data to configure the service its info
+ // If the XML manifest had data to configure the AccessibilityService, its info
// should be already set. In such a case update only the dynamically
// configurable properties.
boolean oldRequestIme = mRequestImeApis;
@@ -1733,40 +1746,40 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
try {
// Clear the proxy in the other process so this
// IAccessibilityServiceConnection can be garbage collected.
- if (mServiceInterface != null) {
+ if (mClient != null) {
if (svcClientTracingEnabled()) {
logTraceSvcClient("init", "null, " + mId + ", null");
}
- mServiceInterface.init(null, mId, null);
+ mClient.init(null, mId, null);
}
} catch (RemoteException re) {
/* ignore */
}
- if (mService != null) {
+ if (mClientBinder != null) {
try {
- mService.unlinkToDeath(this, 0);
+ mClientBinder.unlinkToDeath(this, 0);
} catch (NoSuchElementException e) {
Slog.e(LOG_TAG, "Failed unregistering death link");
}
- mService = null;
+ mClientBinder = null;
}
- mServiceInterface = null;
+ mClient = null;
mReceivedAccessibilityButtonCallbackSinceBind = false;
}
public boolean isConnectedLocked() {
- return (mService != null);
+ return (mClientBinder != null);
}
public void notifyAccessibilityEvent(AccessibilityEvent event) {
synchronized (mLock) {
final int eventType = event.getEventType();
- final boolean serviceWantsEvent = wantsEventLocked(event);
+ final boolean clientWantsEvent = clientWantsEventLocked(event);
final boolean requiredForCacheConsistency = mUsesAccessibilityCache
&& ((AccessibilityCache.CACHE_CRITICAL_EVENTS_MASK & eventType) != 0);
- if (!serviceWantsEvent && !requiredForCacheConsistency) {
+ if (!clientWantsEvent && !requiredForCacheConsistency) {
return;
}
@@ -1774,7 +1787,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
return;
}
// Make a copy since during dispatch it is possible the event to
- // be modified to remove its source if the receiving service does
+ // be modified to remove its source if the receiving client does
// not have permission to access the window content.
AccessibilityEvent newEvent = AccessibilityEvent.obtain(event);
Message message;
@@ -1792,22 +1805,20 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
// Send all messages, bypassing mPendingEvents
message = mEventDispatchHandler.obtainMessage(eventType, newEvent);
}
- message.arg1 = serviceWantsEvent ? 1 : 0;
+ message.arg1 = clientWantsEvent ? 1 : 0;
mEventDispatchHandler.sendMessageDelayed(message, mNotificationTimeout);
}
}
/**
- * Determines if given event can be dispatched to a service based on the package of the
- * event source. Specifically, a service is notified if it is interested in events from the
- * package.
+ * Determines if given event can be dispatched to a client based on the package of the event
+ * source. Specifically, a client is notified if it is interested in events from the package.
*
* @param event The event.
- * @return True if the listener should be notified, false otherwise.
+ * @return True if the client should be notified, false otherwise.
*/
- private boolean wantsEventLocked(AccessibilityEvent event) {
-
+ private boolean clientWantsEventLocked(AccessibilityEvent event) {
if (!canReceiveEventsLocked()) {
return false;
}
@@ -1838,22 +1849,20 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
}
/**
- * Notifies an accessibility service client for a scheduled event given the event type.
+ * Notifies a client for a scheduled event given the event type.
*
* @param eventType The type of the event to dispatch.
*/
private void notifyAccessibilityEventInternal(
- int eventType,
- AccessibilityEvent event,
- boolean serviceWantsEvent) {
- IAccessibilityServiceClient listener;
+ int eventType, AccessibilityEvent event, boolean clientWantsEvent) {
+ IAccessibilityServiceClient client;
synchronized (mLock) {
- listener = mServiceInterface;
+ client = mClient;
- // If the service died/was disabled while the message for dispatching
- // the accessibility event was propagating the listener may be null.
- if (listener == null) {
+ // If the client (in the application process) died/was disabled while the message for
+ // dispatching the accessibility event was propagating, "client" may be null.
+ if (client == null) {
return;
}
@@ -1868,7 +1877,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
// 1) A binder thread calls notifyAccessibilityServiceDelayedLocked
// which posts a message for dispatching an event and stores the event
// in mPendingEvents.
- // 2) The message is pulled from the queue by the handler on the service
+ // 2) The message is pulled from the queue by the handler on the client
// thread and this method is just about to acquire the lock.
// 3) Another binder thread acquires the lock in notifyAccessibilityEvent
// 4) notifyAccessibilityEvent recycles the event that this method was about
@@ -1876,7 +1885,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
// 5) This method grabs the new event, processes it, and removes it from
// mPendingEvents
// 6) The second message dispatched in (4) arrives, but the event has been
- // remvoved in (5).
+ // removed in (5).
event = mPendingEvents.get(eventType);
if (event == null) {
return;
@@ -1893,14 +1902,14 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
try {
if (svcClientTracingEnabled()) {
- logTraceSvcClient("onAccessibilityEvent", event + ";" + serviceWantsEvent);
+ logTraceSvcClient("onAccessibilityEvent", event + ";" + clientWantsEvent);
}
- listener.onAccessibilityEvent(event, serviceWantsEvent);
+ client.onAccessibilityEvent(event, clientWantsEvent);
if (DEBUG) {
- Slog.i(LOG_TAG, "Event " + event + " sent to " + listener);
+ Slog.i(LOG_TAG, "Event " + event + " sent to " + client);
}
} catch (RemoteException re) {
- Slog.e(LOG_TAG, "Error during sending " + event + " to " + listener, re);
+ Slog.e(LOG_TAG, "Error during sending " + event + " to " + client, re);
} finally {
event.recycle();
}
@@ -1978,122 +1987,126 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
return (mGenericMotionEventSources & eventSourceWithoutClass) != 0;
}
-
/**
- * Called by the invocation handler to notify the service that the
- * state of magnification has changed.
+ * Called by the invocation handler to notify the client that the state of magnification has
+ * changed.
*/
- private void notifyMagnificationChangedInternal(int displayId, @NonNull Region region,
- @NonNull MagnificationConfig config) {
- final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
- if (listener != null) {
+ private void notifyMagnificationChangedInternal(
+ int displayId, @NonNull Region region, @NonNull MagnificationConfig config) {
+ final IAccessibilityServiceClient client = getClientSafely();
+ if (client != null) {
try {
if (svcClientTracingEnabled()) {
logTraceSvcClient("onMagnificationChanged", displayId + ", " + region + ", "
+ config.toString());
}
- listener.onMagnificationChanged(displayId, region, config);
+ client.onMagnificationChanged(displayId, region, config);
} catch (RemoteException re) {
- Slog.e(LOG_TAG, "Error sending magnification changes to " + mService, re);
+ Slog.e(LOG_TAG, "Error sending magnification changes to " + mClientBinder, re);
}
}
}
/**
- * Called by the invocation handler to notify the service that the state of the soft
- * keyboard show mode has changed.
+ * Called by the invocation handler to notify the client that the state of the soft keyboard
+ * show mode has changed.
*/
private void notifySoftKeyboardShowModeChangedInternal(int showState) {
- final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
- if (listener != null) {
+ final IAccessibilityServiceClient client = getClientSafely();
+ if (client != null) {
try {
if (svcClientTracingEnabled()) {
logTraceSvcClient("onSoftKeyboardShowModeChanged", String.valueOf(showState));
}
- listener.onSoftKeyboardShowModeChanged(showState);
+ client.onSoftKeyboardShowModeChanged(showState);
} catch (RemoteException re) {
- Slog.e(LOG_TAG, "Error sending soft keyboard show mode changes to " + mService,
+ Slog.e(
+ LOG_TAG,
+ "Error sending soft keyboard show mode changes to " + mClientBinder,
re);
}
}
}
private void notifyAccessibilityButtonClickedInternal(int displayId) {
- final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
- if (listener != null) {
+ final IAccessibilityServiceClient client = getClientSafely();
+ if (client != null) {
try {
if (svcClientTracingEnabled()) {
logTraceSvcClient("onAccessibilityButtonClicked", String.valueOf(displayId));
}
- listener.onAccessibilityButtonClicked(displayId);
+ client.onAccessibilityButtonClicked(displayId);
} catch (RemoteException re) {
- Slog.e(LOG_TAG, "Error sending accessibility button click to " + mService, re);
+ Slog.e(LOG_TAG, "Error sending accessibility button click to " + mClientBinder, re);
}
}
}
private void notifyAccessibilityButtonAvailabilityChangedInternal(boolean available) {
- // Only notify the service if it's not been notified or the state has changed
+ // Only notify the client if it's not been notified or the state has changed
if (mReceivedAccessibilityButtonCallbackSinceBind
&& (mLastAccessibilityButtonCallbackState == available)) {
return;
}
mReceivedAccessibilityButtonCallbackSinceBind = true;
mLastAccessibilityButtonCallbackState = available;
- final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
- if (listener != null) {
+ final IAccessibilityServiceClient client = getClientSafely();
+ if (client != null) {
try {
if (svcClientTracingEnabled()) {
logTraceSvcClient("onAccessibilityButtonAvailabilityChanged",
String.valueOf(available));
}
- listener.onAccessibilityButtonAvailabilityChanged(available);
+ client.onAccessibilityButtonAvailabilityChanged(available);
} catch (RemoteException re) {
- Slog.e(LOG_TAG,
- "Error sending accessibility button availability change to " + mService,
+ Slog.e(
+ LOG_TAG,
+ "Error sending accessibility button availability change to "
+ + mClientBinder,
re);
}
}
}
private void notifyGestureInternal(AccessibilityGestureEvent gestureInfo) {
- final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
- if (listener != null) {
+ final IAccessibilityServiceClient client = getClientSafely();
+ if (client != null) {
try {
if (svcClientTracingEnabled()) {
logTraceSvcClient("onGesture", gestureInfo.toString());
}
- listener.onGesture(gestureInfo);
+ client.onGesture(gestureInfo);
} catch (RemoteException re) {
- Slog.e(LOG_TAG, "Error during sending gesture " + gestureInfo
- + " to " + mService, re);
+ Slog.e(
+ LOG_TAG,
+ "Error during sending gesture " + gestureInfo + " to " + mClientBinder,
+ re);
}
}
}
private void notifySystemActionsChangedInternal() {
- final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
- if (listener != null) {
+ final IAccessibilityServiceClient client = getClientSafely();
+ if (client != null) {
try {
if (svcClientTracingEnabled()) {
logTraceSvcClient("onSystemActionsChanged", "");
}
- listener.onSystemActionsChanged();
+ client.onSystemActionsChanged();
} catch (RemoteException re) {
- Slog.e(LOG_TAG, "Error sending system actions change to " + mService,
- re);
+ Slog.e(LOG_TAG, "Error sending system actions change to " + mClientBinder, re);
}
}
}
private void notifyClearAccessibilityCacheInternal() {
- final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
- if (listener != null) {
+ final IAccessibilityServiceClient client = getClientSafely();
+ if (client != null) {
try {
if (svcClientTracingEnabled()) {
logTraceSvcClient("clearAccessibilityCache", "");
}
- listener.clearAccessibilityCache();
+ client.clearAccessibilityCache();
} catch (RemoteException re) {
Slog.e(LOG_TAG, "Error during requesting accessibility info cache"
+ " to be cleared.", re);
@@ -2106,70 +2119,66 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
private void setImeSessionEnabledInternal(IAccessibilityInputMethodSession session,
boolean enabled) {
- final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
- if (listener != null && session != null) {
+ final IAccessibilityServiceClient client = getClientSafely();
+ if (client != null && session != null) {
try {
if (svcClientTracingEnabled()) {
logTraceSvcClient("createImeSession", "");
}
- listener.setImeSessionEnabled(session, enabled);
+ client.setImeSessionEnabled(session, enabled);
} catch (RemoteException re) {
- Slog.e(LOG_TAG,
- "Error requesting IME session from " + mService, re);
+ Slog.e(LOG_TAG, "Error requesting IME session from " + mClientBinder, re);
}
}
}
private void bindInputInternal() {
- final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
- if (listener != null) {
+ final IAccessibilityServiceClient client = getClientSafely();
+ if (client != null) {
try {
if (svcClientTracingEnabled()) {
logTraceSvcClient("bindInput", "");
}
- listener.bindInput();
+ client.bindInput();
} catch (RemoteException re) {
- Slog.e(LOG_TAG,
- "Error binding input to " + mService, re);
+ Slog.e(LOG_TAG, "Error binding input to " + mClientBinder, re);
}
}
}
private void unbindInputInternal() {
- final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
- if (listener != null) {
+ final IAccessibilityServiceClient client = getClientSafely();
+ if (client != null) {
try {
if (svcClientTracingEnabled()) {
logTraceSvcClient("unbindInput", "");
}
- listener.unbindInput();
+ client.unbindInput();
} catch (RemoteException re) {
- Slog.e(LOG_TAG,
- "Error unbinding input to " + mService, re);
+ Slog.e(LOG_TAG, "Error unbinding input to " + mClientBinder, re);
}
}
}
private void startInputInternal(IRemoteAccessibilityInputConnection connection,
EditorInfo editorInfo, boolean restarting) {
- final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
- if (listener != null) {
+ final IAccessibilityServiceClient client = getClientSafely();
+ if (client != null) {
try {
if (svcClientTracingEnabled()) {
logTraceSvcClient("startInput", "editorInfo=" + editorInfo
+ " restarting=" + restarting);
}
- listener.startInput(connection, editorInfo, restarting);
+ client.startInput(connection, editorInfo, restarting);
} catch (RemoteException re) {
- Slog.e(LOG_TAG,
- "Error starting input to " + mService, re);
+ Slog.e(LOG_TAG, "Error starting input to " + mClientBinder, re);
}
}
}
- protected IAccessibilityServiceClient getServiceInterfaceSafely() {
+ protected IAccessibilityServiceClient getClientSafely() {
synchronized (mLock) {
- return mServiceInterface;
+ return mClient;
}
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 114541222995..d595d02016e0 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -1435,8 +1435,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
interfacesToInterrupt = new ArrayList<>(services.size());
for (int i = 0; i < services.size(); i++) {
AccessibilityServiceConnection service = services.get(i);
- IBinder a11yServiceBinder = service.mService;
- IAccessibilityServiceClient a11yServiceInterface = service.mServiceInterface;
+ IBinder a11yServiceBinder = service.mClientBinder;
+ IAccessibilityServiceClient a11yServiceInterface = service.mClient;
if ((a11yServiceBinder != null) && (a11yServiceInterface != null)) {
interfacesToInterrupt.add(a11yServiceInterface);
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
index 786d167af5de..15999d19ebc0 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
@@ -166,8 +166,9 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect
if (userState.getBindInstantServiceAllowedLocked()) {
flags |= Context.BIND_ALLOW_INSTANT;
}
- if (mService == null && mContext.bindServiceAsUser(
- mIntent, this, flags, new UserHandle(userState.mUserId))) {
+ if (mClientBinder == null
+ && mContext.bindServiceAsUser(
+ mIntent, this, flags, new UserHandle(userState.mUserId))) {
userState.getBindingServicesLocked().add(mComponentName);
}
} finally {
@@ -227,20 +228,20 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect
addWindowTokensForAllDisplays();
}
synchronized (mLock) {
- if (mService != service) {
- if (mService != null) {
- mService.unlinkToDeath(this, 0);
+ if (mClientBinder != service) {
+ if (mClientBinder != null) {
+ mClientBinder.unlinkToDeath(this, 0);
}
- mService = service;
+ mClientBinder = service;
try {
- mService.linkToDeath(this, 0);
+ mClientBinder.linkToDeath(this, 0);
} catch (RemoteException re) {
Slog.e(LOG_TAG, "Failed registering death link");
binderDied();
return;
}
}
- mServiceInterface = IAccessibilityServiceClient.Stub.asInterface(service);
+ mClient = IAccessibilityServiceClient.Stub.asInterface(service);
if (userState == null) return;
userState.addServiceLocked(this);
mSystemSupport.onClientChangeLocked(false);
@@ -261,7 +262,7 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect
}
private void initializeService() {
- IAccessibilityServiceClient serviceInterface = null;
+ IAccessibilityServiceClient client = null;
synchronized (mLock) {
AccessibilityUserState userState = mUserStateWeakReference.get();
if (userState == null) return;
@@ -272,18 +273,17 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect
bindingServices.remove(mComponentName);
crashedServices.remove(mComponentName);
mAccessibilityServiceInfo.crashed = false;
- serviceInterface = mServiceInterface;
+ client = mClient;
}
// There's a chance that service is removed from enabled_accessibility_services setting
// key, but skip unbinding because of it's in binding state. Unbinds it if it's
// not in enabled service list.
- if (serviceInterface != null
- && !userState.getEnabledServicesLocked().contains(mComponentName)) {
+ if (client != null && !userState.getEnabledServicesLocked().contains(mComponentName)) {
mSystemSupport.onClientChangeLocked(false);
return;
}
}
- if (serviceInterface == null) {
+ if (client == null) {
binderDied();
return;
}
@@ -292,10 +292,9 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect
logTraceSvcClient("init",
this + "," + mId + "," + mOverlayWindowTokens.get(Display.DEFAULT_DISPLAY));
}
- serviceInterface.init(this, mId, mOverlayWindowTokens.get(Display.DEFAULT_DISPLAY));
+ client.init(this, mId, mOverlayWindowTokens.get(Display.DEFAULT_DISPLAY));
} catch (RemoteException re) {
- Slog.w(LOG_TAG, "Error while setting connection for service: "
- + serviceInterface, re);
+ Slog.w(LOG_TAG, "Error while setting connection for service: " + client, re);
binderDied();
}
}
@@ -496,7 +495,7 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect
@Override
public boolean isCapturingFingerprintGestures() {
- return (mServiceInterface != null)
+ return (mClient != null)
&& mSecurityPolicy.canCaptureFingerprintGestures(this)
&& mCaptureFingerprintGestures;
}
@@ -506,17 +505,17 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect
if (!isCapturingFingerprintGestures()) {
return;
}
- IAccessibilityServiceClient serviceInterface;
+ IAccessibilityServiceClient client;
synchronized (mLock) {
- serviceInterface = mServiceInterface;
+ client = mClient;
}
- if (serviceInterface != null) {
+ if (client != null) {
try {
if (svcClientTracingEnabled()) {
logTraceSvcClient(
"onFingerprintCapturingGesturesChanged", String.valueOf(active));
}
- mServiceInterface.onFingerprintCapturingGesturesChanged(active);
+ mClient.onFingerprintCapturingGesturesChanged(active);
} catch (RemoteException e) {
}
}
@@ -527,16 +526,16 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect
if (!isCapturingFingerprintGestures()) {
return;
}
- IAccessibilityServiceClient serviceInterface;
+ IAccessibilityServiceClient client;
synchronized (mLock) {
- serviceInterface = mServiceInterface;
+ client = mClient;
}
- if (serviceInterface != null) {
+ if (client != null) {
try {
if (svcClientTracingEnabled()) {
logTraceSvcClient("onFingerprintGesture", String.valueOf(gesture));
}
- mServiceInterface.onFingerprintGesture(gesture);
+ mClient.onFingerprintGesture(gesture);
} catch (RemoteException e) {
}
}
@@ -546,7 +545,7 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect
@Override
public void dispatchGesture(int sequence, ParceledListSlice gestureSteps, int displayId) {
synchronized (mLock) {
- if (mServiceInterface != null && mSecurityPolicy.canPerformGestures(this)) {
+ if (mClient != null && mSecurityPolicy.canPerformGestures(this)) {
final long identity = Binder.clearCallingIdentity();
try {
MotionEventInjector motionEventInjector =
@@ -557,16 +556,18 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect
if (motionEventInjector != null
&& mWindowManagerService.isTouchOrFaketouchDevice()) {
motionEventInjector.injectEvents(
- gestureSteps.getList(), mServiceInterface, sequence, displayId);
+ gestureSteps.getList(), mClient, sequence, displayId);
} else {
try {
if (svcClientTracingEnabled()) {
logTraceSvcClient("onPerformGestureResult", sequence + ", false");
}
- mServiceInterface.onPerformGestureResult(sequence, false);
+ mClient.onPerformGestureResult(sequence, false);
} catch (RemoteException re) {
- Slog.e(LOG_TAG, "Error sending motion event injection failure to "
- + mServiceInterface, re);
+ Slog.e(
+ LOG_TAG,
+ "Error sending motion event injection failure to " + mClient,
+ re);
}
}
} finally {
@@ -631,48 +632,47 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect
@Override
protected void createImeSessionInternal() {
- final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
- if (listener != null) {
+ final IAccessibilityServiceClient client = getClientSafely();
+ if (client != null) {
try {
if (svcClientTracingEnabled()) {
logTraceSvcClient("createImeSession", "");
}
AccessibilityInputMethodSessionCallback
callback = new AccessibilityInputMethodSessionCallback(mUserId);
- listener.createImeSession(callback);
+ client.createImeSession(callback);
} catch (RemoteException re) {
- Slog.e(LOG_TAG,
- "Error requesting IME session from " + mService, re);
+ Slog.e(LOG_TAG, "Error requesting IME session from " + mClientBinder, re);
}
}
}
private void notifyMotionEventInternal(MotionEvent event) {
- final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
- if (listener != null) {
+ final IAccessibilityServiceClient client = getClientSafely();
+ if (client != null) {
try {
if (mTrace.isA11yTracingEnabled()) {
logTraceSvcClient(".onMotionEvent ",
event.toString());
}
- listener.onMotionEvent(event);
+ client.onMotionEvent(event);
} catch (RemoteException re) {
- Slog.e(LOG_TAG, "Error sending motion event to" + mService, re);
+ Slog.e(LOG_TAG, "Error sending motion event to" + mClientBinder, re);
}
}
}
private void notifyTouchStateInternal(int displayId, int state) {
- final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
- if (listener != null) {
+ final IAccessibilityServiceClient client = getClientSafely();
+ if (client != null) {
try {
if (mTrace.isA11yTracingEnabled()) {
logTraceSvcClient(".onTouchStateChanged ",
TouchInteractionController.stateToString(state));
}
- listener.onTouchStateChanged(displayId, state);
+ client.onTouchStateChanged(displayId, state);
} catch (RemoteException re) {
- Slog.e(LOG_TAG, "Error sending motion event to" + mService, re);
+ Slog.e(LOG_TAG, "Error sending motion event to" + mClientBinder, re);
}
}
}
diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
index 4cb3d247edb0..cd97d838e3a0 100644
--- a/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
@@ -109,14 +109,11 @@ public class ProxyAccessibilityServiceConnection extends AccessibilityServiceCon
return mDeviceId;
}
- /**
- * Called when the proxy is registered.
- */
- void initializeServiceInterface(IAccessibilityServiceClient serviceInterface)
- throws RemoteException {
- mServiceInterface = serviceInterface;
- mService = serviceInterface.asBinder();
- mServiceInterface.init(this, mId, this.mOverlayWindowTokens.get(mDisplayId));
+ /** Called when the proxy is registered. */
+ void initializeClient(IAccessibilityServiceClient client) throws RemoteException {
+ mClient = client;
+ mClientBinder = client.asBinder();
+ mClient.init(this, mId, this.mOverlayWindowTokens.get(mDisplayId));
}
/**
diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
index b4deeb0a6872..da11a76d5282 100644
--- a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
@@ -214,7 +214,7 @@ public class ProxyManager {
mA11yInputFilter.disableFeaturesForDisplayIfInstalled(displayId);
}
});
- connection.initializeServiceInterface(client);
+ connection.initializeClient(client);
}
private void registerVirtualDeviceListener() {
@@ -561,8 +561,8 @@ public class ProxyManager {
final ProxyAccessibilityServiceConnection proxy =
mProxyA11yServiceConnections.valueAt(i);
if (proxy != null && proxy.getDeviceId() == deviceId) {
- final IBinder proxyBinder = proxy.mService;
- final IAccessibilityServiceClient proxyInterface = proxy.mServiceInterface;
+ final IBinder proxyBinder = proxy.mClientBinder;
+ final IAccessibilityServiceClient proxyInterface = proxy.mClient;
if ((proxyBinder != null) && (proxyInterface != null)) {
interfaces.add(proxyInterface);
}
diff --git a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
index f85d786f89c5..ed4eeb534412 100644
--- a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
@@ -107,8 +107,7 @@ class UiAutomationManager {
Binder.getCallingUserHandle().getIdentifier());
if (mUiAutomationService != null) {
throw new IllegalStateException(
- "UiAutomationService " + mUiAutomationService.mServiceInterface
- + "already registered!");
+ "UiAutomationService " + mUiAutomationService.mClient + "already registered!");
}
try {
@@ -130,10 +129,9 @@ class UiAutomationManager {
mainHandler, mLock, securityPolicy, systemSupport, trace, windowManagerInternal,
systemActionPerformer, awm);
mUiAutomationServiceOwner = owner;
- mUiAutomationService.mServiceInterface = serviceClient;
+ mUiAutomationService.mClient = serviceClient;
try {
- mUiAutomationService.mServiceInterface.asBinder().linkToDeath(mUiAutomationService,
- 0);
+ mUiAutomationService.mClient.asBinder().linkToDeath(mUiAutomationService, 0);
} catch (RemoteException re) {
Slog.e(LOG_TAG, "Failed registering death link: " + re);
destroyUiAutomationService();
@@ -149,10 +147,10 @@ class UiAutomationManager {
synchronized (mLock) {
if (useAccessibility()
&& ((mUiAutomationService == null)
- || (serviceClient == null)
- || (mUiAutomationService.mServiceInterface == null)
- || (serviceClient.asBinder()
- != mUiAutomationService.mServiceInterface.asBinder()))) {
+ || (serviceClient == null)
+ || (mUiAutomationService.mClient == null)
+ || (serviceClient.asBinder()
+ != mUiAutomationService.mClient.asBinder()))) {
throw new IllegalStateException("UiAutomationService " + serviceClient
+ " not registered!");
}
@@ -230,8 +228,7 @@ class UiAutomationManager {
private void destroyUiAutomationService() {
synchronized (mLock) {
if (mUiAutomationService != null) {
- mUiAutomationService.mServiceInterface.asBinder().unlinkToDeath(
- mUiAutomationService, 0);
+ mUiAutomationService.mClient.asBinder().unlinkToDeath(mUiAutomationService, 0);
mUiAutomationService.onRemoved();
mUiAutomationService.resetLocked();
mUiAutomationService = null;
@@ -271,40 +268,48 @@ class UiAutomationManager {
void connectServiceUnknownThread() {
// This needs to be done on the main thread
- mMainHandler.post(() -> {
- try {
- final IAccessibilityServiceClient serviceInterface;
- final UiAutomationService uiAutomationService;
- synchronized (mLock) {
- serviceInterface = mServiceInterface;
- uiAutomationService = mUiAutomationService;
- if (serviceInterface == null) {
- mService = null;
- } else {
- mService = mServiceInterface.asBinder();
- mService.linkToDeath(this, 0);
+ mMainHandler.post(
+ () -> {
+ try {
+ final IAccessibilityServiceClient client;
+ final UiAutomationService uiAutomationService;
+ synchronized (mLock) {
+ client = mClient;
+ uiAutomationService = mUiAutomationService;
+ if (client == null) {
+ mClientBinder = null;
+ } else {
+ mClientBinder = mClient.asBinder();
+ mClientBinder.linkToDeath(this, 0);
+ }
+ }
+ // If the client is null, the UiAutomation has been shut down on
+ // another thread.
+ if (client != null && uiAutomationService != null) {
+ uiAutomationService.addWindowTokensForAllDisplays();
+ if (mTrace.isA11yTracingEnabledForTypes(
+ AccessibilityTrace.FLAGS_ACCESSIBILITY_SERVICE_CLIENT)) {
+ mTrace.logTrace(
+ "UiAutomationService.connectServiceUnknownThread",
+ AccessibilityTrace.FLAGS_ACCESSIBILITY_SERVICE_CLIENT,
+ "serviceConnection="
+ + this
+ + ";connectionId="
+ + mId
+ + "windowToken="
+ + mOverlayWindowTokens.get(
+ Display.DEFAULT_DISPLAY));
+ }
+ client.init(
+ this,
+ mId,
+ mOverlayWindowTokens.get(Display.DEFAULT_DISPLAY));
+ }
+ } catch (RemoteException re) {
+ Slog.w(LOG_TAG, "Error initializing connection", re);
+ destroyUiAutomationService();
}
- }
- // If the serviceInterface is null, the UiAutomation has been shut down on
- // another thread.
- if (serviceInterface != null && uiAutomationService != null) {
- uiAutomationService.addWindowTokensForAllDisplays();
- if (mTrace.isA11yTracingEnabledForTypes(
- AccessibilityTrace.FLAGS_ACCESSIBILITY_SERVICE_CLIENT)) {
- mTrace.logTrace("UiAutomationService.connectServiceUnknownThread",
- AccessibilityTrace.FLAGS_ACCESSIBILITY_SERVICE_CLIENT,
- "serviceConnection=" + this + ";connectionId=" + mId
- + "windowToken="
- + mOverlayWindowTokens.get(Display.DEFAULT_DISPLAY));
- }
- serviceInterface.init(this, mId,
- mOverlayWindowTokens.get(Display.DEFAULT_DISPLAY));
- }
- } catch (RemoteException re) {
- Slog.w(LOG_TAG, "Error initializing connection", re);
- destroyUiAutomationService();
- }
- });
+ });
}
@Override
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
index c8f8c2a6b223..b6c8fc7b80c7 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
@@ -16,20 +16,35 @@
package com.android.server.appfunctions;
+import static android.app.appfunctions.AppFunctionManager.APP_FUNCTION_STATE_DISABLED;
+import static android.app.appfunctions.AppFunctionManager.APP_FUNCTION_STATE_ENABLED;
+import static android.app.appfunctions.AppFunctionRuntimeMetadata.APP_FUNCTION_RUNTIME_METADATA_DB;
+import static android.app.appfunctions.AppFunctionRuntimeMetadata.APP_FUNCTION_RUNTIME_NAMESPACE;
+
import static com.android.server.appfunctions.AppFunctionExecutors.THREAD_POOL_EXECUTOR;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.WorkerThread;
+import android.app.appfunctions.AppFunctionManager;
+import android.app.appfunctions.AppFunctionManagerHelper;
+import android.app.appfunctions.AppFunctionRuntimeMetadata;
import android.app.appfunctions.AppFunctionStaticMetadataHelper;
import android.app.appfunctions.ExecuteAppFunctionAidlRequest;
import android.app.appfunctions.ExecuteAppFunctionResponse;
+import android.app.appfunctions.IAppFunctionEnabledCallback;
import android.app.appfunctions.IAppFunctionManager;
import android.app.appfunctions.IAppFunctionService;
+import android.app.appfunctions.ICancellationCallback;
import android.app.appfunctions.IExecuteAppFunctionCallback;
import android.app.appfunctions.SafeOneTimeExecuteAppFunctionCallback;
+import android.app.appsearch.AppSearchBatchResult;
import android.app.appsearch.AppSearchManager;
+import android.app.appsearch.AppSearchManager.SearchContext;
import android.app.appsearch.AppSearchResult;
+import android.app.appsearch.GenericDocument;
+import android.app.appsearch.GetByDocumentIdRequest;
+import android.app.appsearch.PutDocumentsRequest;
import android.app.appsearch.observer.DocumentChangeInfo;
import android.app.appsearch.observer.ObserverCallback;
import android.app.appsearch.observer.ObserverSpec;
@@ -37,17 +52,25 @@ import android.app.appsearch.observer.SchemaChangeInfo;
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
+import android.os.CancellationSignal;
+import android.os.ICancellationSignal;
+import android.os.OutcomeReceiver;
+import android.os.ParcelableException;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Slog;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.infra.AndroidFuture;
import com.android.server.SystemService.TargetUser;
import com.android.server.appfunctions.RemoteServiceCaller.RunServiceCallCallback;
import com.android.server.appfunctions.RemoteServiceCaller.ServiceUsageCompleteListener;
import java.util.Objects;
import java.util.concurrent.CompletionException;
+import java.util.concurrent.Executor;
/** Implementation of the AppFunctionManagerService. */
public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
@@ -58,6 +81,7 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
private final ServiceHelper mInternalServiceHelper;
private final ServiceConfig mServiceConfig;
private final Context mContext;
+ private final Object mLock = new Object();
public AppFunctionManagerServiceImpl(@NonNull Context context) {
this(
@@ -99,7 +123,7 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
}
@Override
- public void executeAppFunction(
+ public ICancellationSignal executeAppFunction(
@NonNull ExecuteAppFunctionAidlRequest requestInternal,
@NonNull IExecuteAppFunctionCallback executeAppFunctionCallback) {
Objects.requireNonNull(requestInternal);
@@ -120,11 +144,14 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
ExecuteAppFunctionResponse.RESULT_DENIED,
exception.getMessage(),
/* extras= */ null));
- return;
+ return null;
}
int callingUid = Binder.getCallingUid();
- int callingPid = Binder.getCallingUid();
+ int callingPid = Binder.getCallingPid();
+
+ ICancellationSignal localCancelTransport = CancellationSignal.createTransport();
+
THREAD_POOL_EXECUTOR.execute(
() -> {
try {
@@ -132,12 +159,14 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
requestInternal,
callingUid,
callingPid,
+ localCancelTransport,
safeExecuteAppFunctionCallback);
} catch (Exception e) {
safeExecuteAppFunctionCallback.onResult(
mapExceptionToExecuteAppFunctionResponse(e));
}
});
+ return localCancelTransport;
}
@WorkerThread
@@ -145,6 +174,7 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
ExecuteAppFunctionAidlRequest requestInternal,
int callingUid,
int callingPid,
+ ICancellationSignal localCancelTransport,
SafeOneTimeExecuteAppFunctionCallback safeExecuteAppFunctionCallback) {
UserHandle targetUser = requestInternal.getUserHandle();
// TODO(b/354956319): Add and honor the new enterprise policies.
@@ -168,59 +198,233 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
return;
}
- var unused =
- mCallerValidator
- .verifyCallerCanExecuteAppFunction(
- callingUid,
- callingPid,
- requestInternal.getCallingPackage(),
- targetPackageName,
- requestInternal.getClientRequest().getFunctionIdentifier())
- .thenAccept(
- canExecute -> {
- if (!canExecute) {
- safeExecuteAppFunctionCallback.onResult(
- ExecuteAppFunctionResponse.newFailure(
- ExecuteAppFunctionResponse.RESULT_DENIED,
- "Caller does not have permission to execute"
- + " the appfunction",
- /* extras= */ null));
- return;
- }
- Intent serviceIntent =
- mInternalServiceHelper.resolveAppFunctionService(
- targetPackageName, targetUser);
- if (serviceIntent == null) {
- safeExecuteAppFunctionCallback.onResult(
- ExecuteAppFunctionResponse.newFailure(
- ExecuteAppFunctionResponse
- .RESULT_INTERNAL_ERROR,
- "Cannot find the target service.",
- /* extras= */ null));
- return;
- }
- bindAppFunctionServiceUnchecked(
- requestInternal,
- serviceIntent,
- targetUser,
- safeExecuteAppFunctionCallback,
- /* bindFlags= */ Context.BIND_AUTO_CREATE
- | Context.BIND_FOREGROUND_SERVICE);
- })
- .exceptionally(
- ex -> {
- safeExecuteAppFunctionCallback.onResult(
- mapExceptionToExecuteAppFunctionResponse(ex));
- return null;
- });
+ mCallerValidator
+ .verifyCallerCanExecuteAppFunction(
+ callingUid,
+ callingPid,
+ requestInternal.getCallingPackage(),
+ targetPackageName,
+ requestInternal.getClientRequest().getFunctionIdentifier())
+ .thenAccept(
+ canExecute -> {
+ if (!canExecute) {
+ safeExecuteAppFunctionCallback.onResult(
+ ExecuteAppFunctionResponse.newFailure(
+ ExecuteAppFunctionResponse.RESULT_DENIED,
+ "Caller does not have permission to execute the"
+ + " appfunction",
+ /* extras= */ null));
+ }
+ })
+ .thenCompose(
+ isEnabled ->
+ isAppFunctionEnabled(
+ requestInternal.getClientRequest().getFunctionIdentifier(),
+ requestInternal.getClientRequest().getTargetPackageName(),
+ getAppSearchManagerAsUser(requestInternal.getUserHandle()),
+ THREAD_POOL_EXECUTOR))
+ .thenAccept(
+ isEnabled -> {
+ if (!isEnabled) {
+ throw new DisabledAppFunctionException(
+ "The app function is disabled");
+ }
+ })
+ .thenAccept(
+ unused -> {
+ Intent serviceIntent =
+ mInternalServiceHelper.resolveAppFunctionService(
+ targetPackageName, targetUser);
+ if (serviceIntent == null) {
+ safeExecuteAppFunctionCallback.onResult(
+ ExecuteAppFunctionResponse.newFailure(
+ ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR,
+ "Cannot find the target service.",
+ /* extras= */ null));
+ return;
+ }
+ bindAppFunctionServiceUnchecked(
+ requestInternal,
+ serviceIntent,
+ targetUser,
+ localCancelTransport,
+ safeExecuteAppFunctionCallback,
+ /* bindFlags= */ Context.BIND_AUTO_CREATE
+ | Context.BIND_FOREGROUND_SERVICE);
+ })
+ .exceptionally(
+ ex -> {
+ safeExecuteAppFunctionCallback.onResult(
+ mapExceptionToExecuteAppFunctionResponse(ex));
+ return null;
+ });
+ }
+
+ private static AndroidFuture<Boolean> isAppFunctionEnabled(
+ @NonNull String functionIdentifier,
+ @NonNull String targetPackage,
+ @NonNull AppSearchManager appSearchManager,
+ @NonNull Executor executor) {
+ AndroidFuture<Boolean> future = new AndroidFuture<>();
+ AppFunctionManagerHelper.isAppFunctionEnabled(
+ functionIdentifier,
+ targetPackage,
+ appSearchManager,
+ executor,
+ new OutcomeReceiver<>() {
+ @Override
+ public void onResult(@NonNull Boolean result) {
+ future.complete(result);
+ }
+
+ @Override
+ public void onError(@NonNull Exception error) {
+ future.completeExceptionally(error);
+ }
+ });
+ return future;
+ }
+
+ @Override
+ public void setAppFunctionEnabled(
+ @NonNull String callingPackage,
+ @NonNull String functionIdentifier,
+ @NonNull UserHandle userHandle,
+ @AppFunctionManager.EnabledState int enabledState,
+ @NonNull IAppFunctionEnabledCallback callback) {
+ try {
+ mCallerValidator.validateCallingPackage(callingPackage);
+ } catch (SecurityException e) {
+ reportException(callback, e);
+ return;
+ }
+ THREAD_POOL_EXECUTOR.execute(
+ () -> {
+ try {
+ // TODO(357551503): Instead of holding a global lock, hold a per-package
+ // lock.
+ synchronized (mLock) {
+ setAppFunctionEnabledInternalLocked(
+ callingPackage, functionIdentifier, userHandle, enabledState);
+ }
+ callback.onSuccess();
+ } catch (Exception e) {
+ Slog.e(TAG, "Error in setAppFunctionEnabled: ", e);
+ reportException(callback, e);
+ }
+ });
+ }
+
+ private static void reportException(
+ @NonNull IAppFunctionEnabledCallback callback, @NonNull Exception exception) {
+ try {
+ callback.onError(new ParcelableException(exception));
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to report the exception", e);
+ }
+ }
+
+ /**
+ * Sets the enabled status of a specified app function.
+ * <p>
+ * Required to hold a lock to call this function to avoid document changes during the process.
+ */
+ @WorkerThread
+ @GuardedBy("mLock")
+ private void setAppFunctionEnabledInternalLocked(
+ @NonNull String callingPackage,
+ @NonNull String functionIdentifier,
+ @NonNull UserHandle userHandle,
+ @AppFunctionManager.EnabledState int enabledState)
+ throws Exception {
+ AppSearchManager perUserAppSearchManager = getAppSearchManagerAsUser(userHandle);
+
+ if (perUserAppSearchManager == null) {
+ throw new IllegalStateException(
+ "AppSearchManager not found for user:" + userHandle.getIdentifier());
+ }
+ SearchContext runtimeMetadataSearchContext =
+ new SearchContext.Builder(APP_FUNCTION_RUNTIME_METADATA_DB).build();
+
+ try (FutureAppSearchSession runtimeMetadataSearchSession =
+ new FutureAppSearchSessionImpl(
+ perUserAppSearchManager,
+ THREAD_POOL_EXECUTOR,
+ runtimeMetadataSearchContext)) {
+ AppFunctionRuntimeMetadata existingMetadata =
+ new AppFunctionRuntimeMetadata(
+ getRuntimeMetadataGenericDocument(
+ callingPackage,
+ functionIdentifier,
+ runtimeMetadataSearchSession));
+ AppFunctionRuntimeMetadata.Builder newMetadata =
+ new AppFunctionRuntimeMetadata.Builder(existingMetadata);
+ switch (enabledState) {
+ case AppFunctionManager.APP_FUNCTION_STATE_DEFAULT -> {
+ newMetadata.setEnabled(null);
+ }
+ case APP_FUNCTION_STATE_ENABLED -> {
+ newMetadata.setEnabled(true);
+ }
+ case APP_FUNCTION_STATE_DISABLED -> {
+ newMetadata.setEnabled(false);
+ }
+ default ->
+ throw new IllegalArgumentException(
+ "Value of EnabledState is unsupported.");
+ }
+ AppSearchBatchResult<String, Void> putDocumentBatchResult =
+ runtimeMetadataSearchSession
+ .put(
+ new PutDocumentsRequest.Builder()
+ .addGenericDocuments(newMetadata.build())
+ .build())
+ .get();
+ if (!putDocumentBatchResult.isSuccess()) {
+ throw new IllegalStateException("Failed writing updated doc to AppSearch due to "
+ + putDocumentBatchResult);
+ }
+ }
+ }
+
+ @WorkerThread
+ @NonNull
+ private AppFunctionRuntimeMetadata getRuntimeMetadataGenericDocument(
+ @NonNull String packageName,
+ @NonNull String functionId,
+ @NonNull FutureAppSearchSession runtimeMetadataSearchSession)
+ throws Exception {
+ String documentId =
+ AppFunctionRuntimeMetadata.getDocumentIdForAppFunction(packageName, functionId);
+ GetByDocumentIdRequest request =
+ new GetByDocumentIdRequest.Builder(APP_FUNCTION_RUNTIME_NAMESPACE)
+ .addIds(documentId)
+ .build();
+ AppSearchBatchResult<String, GenericDocument> result =
+ runtimeMetadataSearchSession.getByDocumentId(request).get();
+ if (result.isSuccess()) {
+ return new AppFunctionRuntimeMetadata((result.getSuccesses().get(documentId)));
+ }
+ throw new IllegalArgumentException("Function " + functionId + " does not exist");
}
private void bindAppFunctionServiceUnchecked(
@NonNull ExecuteAppFunctionAidlRequest requestInternal,
@NonNull Intent serviceIntent,
@NonNull UserHandle targetUser,
+ @NonNull ICancellationSignal cancellationSignalTransport,
@NonNull SafeOneTimeExecuteAppFunctionCallback safeExecuteAppFunctionCallback,
int bindFlags) {
+ CancellationSignal cancellationSignal =
+ CancellationSignal.fromTransport(cancellationSignalTransport);
+ ICancellationCallback cancellationCallback =
+ new ICancellationCallback.Stub() {
+ @Override
+ public void sendCancellationTransport(
+ @NonNull ICancellationSignal cancellationTransport) {
+ cancellationSignal.setRemote(cancellationTransport);
+ }
+ };
boolean bindServiceResult =
mRemoteServiceCaller.runServiceCall(
serviceIntent,
@@ -236,6 +440,7 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
try {
service.executeAppFunction(
requestInternal.getClientRequest(),
+ cancellationCallback,
new IExecuteAppFunctionCallback.Stub() {
@Override
public void onResult(
@@ -277,24 +482,27 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
}
}
+ private AppSearchManager getAppSearchManagerAsUser(@NonNull UserHandle userHandle) {
+ return mContext.createContextAsUser(userHandle, /* flags= */ 0)
+ .getSystemService(AppSearchManager.class);
+ }
+
private ExecuteAppFunctionResponse mapExceptionToExecuteAppFunctionResponse(Throwable e) {
if (e instanceof CompletionException) {
e = e.getCause();
}
-
- if (e instanceof AppSearchException) {
- AppSearchException appSearchException = (AppSearchException) e;
- return ExecuteAppFunctionResponse.newFailure(
+ int resultCode = ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR;
+ if (e instanceof AppSearchException appSearchException) {
+ resultCode =
mapAppSearchResultFailureCodeToExecuteAppFunctionResponse(
- appSearchException.getResultCode()),
- appSearchException.getMessage(),
- /* extras= */ null);
+ appSearchException.getResultCode());
+ } else if (e instanceof SecurityException) {
+ resultCode = ExecuteAppFunctionResponse.RESULT_DENIED;
+ } else if (e instanceof DisabledAppFunctionException) {
+ resultCode = ExecuteAppFunctionResponse.RESULT_DISABLED;
}
-
return ExecuteAppFunctionResponse.newFailure(
- ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR,
- e.getMessage(),
- /* extras= */ null);
+ resultCode, e.getMessage(), /* extras= */ null);
}
private int mapAppSearchResultFailureCodeToExecuteAppFunctionResponse(int resultCode) {
@@ -409,4 +617,11 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
}
}
}
+
+ /** Throws when executing a disabled app function. */
+ private static class DisabledAppFunctionException extends RuntimeException {
+ private DisabledAppFunctionException(@NonNull String errorMessage) {
+ super(errorMessage);
+ }
+ }
}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/RemoteServiceCallerImpl.java b/services/appfunctions/java/com/android/server/appfunctions/RemoteServiceCallerImpl.java
index 070a99d5bb28..ffca8491abcd 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/RemoteServiceCallerImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/RemoteServiceCallerImpl.java
@@ -65,8 +65,7 @@ public class RemoteServiceCallerImpl<T> implements RemoteServiceCaller<T> {
@NonNull UserHandle userHandle,
@NonNull RunServiceCallCallback<T> callback) {
OneOffServiceConnection serviceConnection =
- new OneOffServiceConnection(
- intent, bindFlags, userHandle, callback);
+ new OneOffServiceConnection(intent, bindFlags, userHandle, callback);
return serviceConnection.bindAndRun();
}
@@ -93,7 +92,7 @@ public class RemoteServiceCallerImpl<T> implements RemoteServiceCaller<T> {
boolean bindServiceResult =
mContext.bindServiceAsUser(mIntent, this, mFlags, mUserHandle);
- if(!bindServiceResult) {
+ if (!bindServiceResult) {
safeUnbind();
}
diff --git a/services/core/java/android/os/BatteryStatsInternal.java b/services/core/java/android/os/BatteryStatsInternal.java
index 0713999d4354..60b826b50045 100644
--- a/services/core/java/android/os/BatteryStatsInternal.java
+++ b/services/core/java/android/os/BatteryStatsInternal.java
@@ -41,6 +41,7 @@ public abstract class BatteryStatsInternal {
public static final int CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER = 3;
public static final int CPU_WAKEUP_SUBSYSTEM_SENSOR = 4;
public static final int CPU_WAKEUP_SUBSYSTEM_CELLULAR_DATA = 5;
+ public static final int CPU_WAKEUP_SUBSYSTEM_BLUETOOTH = 6;
/** @hide */
@IntDef(prefix = {"CPU_WAKEUP_SUBSYSTEM_"}, value = {
@@ -50,6 +51,7 @@ public abstract class BatteryStatsInternal {
CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER,
CPU_WAKEUP_SUBSYSTEM_SENSOR,
CPU_WAKEUP_SUBSYSTEM_CELLULAR_DATA,
+ CPU_WAKEUP_SUBSYSTEM_BLUETOOTH,
})
@Retention(RetentionPolicy.SOURCE)
public @interface CpuWakeupSubsystem {
@@ -99,6 +101,14 @@ public abstract class BatteryStatsInternal {
public abstract void noteCpuWakingNetworkPacket(Network network, long elapsedMillis, int uid);
/**
+ * Informs battery stats of a sysproxy packet that woke up the CPU
+ *
+ * @param uid The uid that received the packet.
+ * @param elapsedMillis The time of the packet's arrival in elapsed timebase.
+ */
+ public abstract void noteCpuWakingBluetoothProxyPacket(int uid, long elapsedMillis);
+
+ /**
* Informs battery stats of binder stats for the given work source UID.
*/
public abstract void noteBinderCallStats(int workSourceUid, long incrementalBinderCallCount,
diff --git a/services/core/java/com/android/server/EventLogTags.logtags b/services/core/java/com/android/server/EventLogTags.logtags
index 5b271a3730fc..7474df2a91ca 100644
--- a/services/core/java/com/android/server/EventLogTags.logtags
+++ b/services/core/java/com/android/server/EventLogTags.logtags
@@ -87,7 +87,7 @@ option java_package com.android.server
# replaces 27510 with a row per notification
27531 notification_visibility (key|3),(visibile|1),(lifespan|1),(freshness|1),(exposure|1),(rank|1)
# a notification emited noise, vibration, or light
-27532 notification_alert (key|3),(buzz|1),(beep|1),(blink|1),(politeness|1)
+27532 notification_alert (key|3),(buzz|1),(beep|1),(blink|1),(politeness|1),(mute_reason|1)
# a notification was added to a autogroup
27533 notification_autogrouped (key|3)
# notification was removed from an autogroup
diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index fbe593fd3df1..682eb768a23e 100644
--- a/services/core/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -25,6 +25,7 @@ import static com.android.server.crashrecovery.CrashRecoveryUtils.dumpCrashRecov
import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -91,6 +92,7 @@ import java.util.concurrent.TimeUnit;
* Monitors the health of packages on the system and notifies interested observers when packages
* fail. On failure, the registered observer with the least user impacting mitigation will
* be notified.
+ * @hide
*/
public class PackageWatchdog {
private static final String TAG = "PackageWatchdog";
@@ -108,13 +110,25 @@ public class PackageWatchdog {
private static final long NUMBER_OF_NATIVE_CRASH_POLLS = 10;
+ /** Reason for package failure could not be determined. */
public static final int FAILURE_REASON_UNKNOWN = 0;
+
+ /** The package had a native crash. */
public static final int FAILURE_REASON_NATIVE_CRASH = 1;
+
+ /** The package failed an explicit health check. */
public static final int FAILURE_REASON_EXPLICIT_HEALTH_CHECK = 2;
+
+ /** The app crashed. */
public static final int FAILURE_REASON_APP_CRASH = 3;
+
+ /** The app was not responding. */
public static final int FAILURE_REASON_APP_NOT_RESPONDING = 4;
+
+ /** The device was boot looping. */
public static final int FAILURE_REASON_BOOT_LOOP = 5;
+ /** @hide */
@IntDef(prefix = { "FAILURE_REASON_" }, value = {
FAILURE_REASON_UNKNOWN,
FAILURE_REASON_NATIVE_CRASH,
@@ -186,7 +200,8 @@ public class PackageWatchdog {
// aborted.
private static final String METADATA_FILE = "/metadata/watchdog/mitigation_count.txt";
- @GuardedBy("PackageWatchdog.class")
+ private static final Object sPackageWatchdogLock = new Object();
+ @GuardedBy("sPackageWatchdogLock")
private static PackageWatchdog sPackageWatchdog;
private final Object mLock = new Object();
@@ -278,8 +293,8 @@ public class PackageWatchdog {
}
/** Creates or gets singleton instance of PackageWatchdog. */
- public static PackageWatchdog getInstance(Context context) {
- synchronized (PackageWatchdog.class) {
+ public static @NonNull PackageWatchdog getInstance(@NonNull Context context) {
+ synchronized (sPackageWatchdogLock) {
if (sPackageWatchdog == null) {
new PackageWatchdog(context);
}
@@ -290,6 +305,7 @@ public class PackageWatchdog {
/**
* Called during boot to notify when packages are ready on the device so we can start
* binding.
+ * @hide
*/
public void onPackagesReady() {
synchronized (mLock) {
@@ -311,6 +327,7 @@ public class PackageWatchdog {
*
* <p>Observers are expected to call this on boot. It does not specify any packages but
* it will resume observing any packages requested from a previous boot.
+ * @hide
*/
public void registerHealthObserver(PackageHealthObserver observer) {
synchronized (mLock) {
@@ -344,6 +361,7 @@ public class PackageWatchdog {
*
* <p>If {@code durationMs} is less than 1, a default monitoring duration
* {@link #DEFAULT_OBSERVING_DURATION_MS} will be used.
+ * @hide
*/
public void startObservingHealth(PackageHealthObserver observer, List<String> packageNames,
long durationMs) {
@@ -407,6 +425,7 @@ public class PackageWatchdog {
* Unregisters {@code observer} from listening to package failure.
* Additionally, this stops observing any packages that may have previously been observed
* even from a previous boot.
+ * @hide
*/
public void unregisterHealthObserver(PackageHealthObserver observer) {
mLongTaskHandler.post(() -> {
@@ -425,7 +444,7 @@ public class PackageWatchdog {
*
* <p>This method could be called frequently if there is a severe problem on the device.
*/
- public void onPackageFailure(List<VersionedPackage> packages,
+ public void onPackageFailure(@NonNull List<VersionedPackage> packages,
@FailureReasons int failureReason) {
if (packages == null) {
Slog.w(TAG, "Could not resolve a list of failing packages");
@@ -566,6 +585,7 @@ public class PackageWatchdog {
*
* Note: PackageWatchdog considers system_server restart loop as bootloop. Full reboots
* are not counted in bootloop.
+ * @hide
*/
@SuppressWarnings("GuardedBy")
public void noteBoot() {
@@ -620,7 +640,7 @@ public class PackageWatchdog {
// TODO(b/120598832): Optimize write? Maybe only write a separate smaller file? Also
// avoid holding lock?
// This currently adds about 7ms extra to shutdown thread
- /** Writes the package information to file during shutdown. */
+ /** @hide Writes the package information to file during shutdown. */
public void writeNow() {
synchronized (mLock) {
// Must only run synchronous tasks as this runs on the ShutdownThread and no other
@@ -674,6 +694,7 @@ public class PackageWatchdog {
* Since this method can eventually trigger a rollback, it should be called
* only once boot has completed {@code onBootCompleted} and not earlier, because the install
* session must be entirely completed before we try to rollback.
+ * @hide
*/
public void scheduleCheckAndMitigateNativeCrashes() {
Slog.i(TAG, "Scheduling " + mNumberOfNativeCrashPollsRemaining + " polls to check "
@@ -695,7 +716,9 @@ public class PackageWatchdog {
return mPackagesExemptFromImpactLevelThreshold;
}
- /** Possible severity values of the user impact of a {@link PackageHealthObserver#execute}. */
+ /** Possible severity values of the user impact of a {@link PackageHealthObserver#execute}.
+ * @hide
+ */
@Retention(SOURCE)
@IntDef(value = {PackageHealthObserverImpact.USER_IMPACT_LEVEL_0,
PackageHealthObserverImpact.USER_IMPACT_LEVEL_10,
@@ -787,7 +810,7 @@ public class PackageWatchdog {
* Identifier for the observer, should not change across device updates otherwise the
* watchdog may drop observing packages with the old name.
*/
- String getUniqueIdentifier();
+ @NonNull String getUniqueIdentifier();
/**
* An observer will not be pruned if this is set, even if the observer is not explicitly
@@ -804,7 +827,7 @@ public class PackageWatchdog {
* <p> A persistent observer may choose to start observing certain failing packages, even if
* it has not explicitly asked to watch the package with {@link #startObservingHealth}.
*/
- default boolean mayObservePackage(String packageName) {
+ default boolean mayObservePackage(@NonNull String packageName) {
return false;
}
}
@@ -1240,7 +1263,7 @@ public class PackageWatchdog {
}
}
- /** Convert a {@code LongArrayQueue} to a String of comma-separated values. */
+ /** @hide Convert a {@code LongArrayQueue} to a String of comma-separated values. */
public static String longArrayQueueToString(LongArrayQueue queue) {
if (queue.size() > 0) {
StringBuilder sb = new StringBuilder();
@@ -1254,7 +1277,7 @@ public class PackageWatchdog {
return "";
}
- /** Parse a comma-separated String of longs into a LongArrayQueue. */
+ /** @hide Parse a comma-separated String of longs into a LongArrayQueue. */
public static LongArrayQueue parseLongArrayQueue(String commaSeparatedValues) {
LongArrayQueue result = new LongArrayQueue();
if (!TextUtils.isEmpty(commaSeparatedValues)) {
@@ -1268,7 +1291,7 @@ public class PackageWatchdog {
/** Dump status of every observer in mAllObservers. */
- public void dump(PrintWriter pw) {
+ public void dump(@NonNull PrintWriter pw) {
IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
ipw.println("Package Watchdog status");
ipw.increaseIndent();
@@ -1395,6 +1418,7 @@ public class PackageWatchdog {
/**
* Increments failure counts of {@code packageName}.
* @returns {@code true} if failure threshold is exceeded, {@code false} otherwise
+ * @hide
*/
@GuardedBy("mLock")
public boolean onPackageFailureLocked(String packageName) {
@@ -1514,6 +1538,7 @@ public class PackageWatchdog {
}
}
+ /** @hide */
@Retention(SOURCE)
@IntDef(value = {
HealthCheckState.ACTIVE,
@@ -1603,7 +1628,9 @@ public class PackageWatchdog {
updateHealthCheckStateLocked();
}
- /** Writes the salient fields to disk using {@code out}. */
+ /** Writes the salient fields to disk using {@code out}.
+ * @hide
+ */
@GuardedBy("mLock")
public void writeLocked(TypedXmlSerializer out) throws IOException {
out.startTag(null, TAG_PACKAGE);
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index d86bae19f174..e64a4803b14f 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -219,7 +219,7 @@ class StorageManagerService extends IStorageManager.Stub
public static final int FAILED_MOUNT_RESET_TIMEOUT_SECONDS = 10;
/** Extended timeout for the system server watchdog. */
- private static final int SLOW_OPERATION_WATCHDOG_TIMEOUT_MS = 20 * 1000;
+ private static final int SLOW_OPERATION_WATCHDOG_TIMEOUT_MS = 30 * 1000;
/** Extended timeout for the system server watchdog for vold#partition operation. */
private static final int PARTITION_OPERATION_WATCHDOG_TIMEOUT_MS = 3 * 60 * 1000;
@@ -3251,7 +3251,7 @@ class StorageManagerService extends IStorageManager.Stub
if (Binder.getCallingUid() != android.os.Process.SYSTEM_UID) {
throw new SecurityException("no permission to commit checkpoint changes");
}
-
+ extendWatchdogTimeout("vold#commitChanges might be slow");
mVold.commitChanges();
}
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 88edb121c0c8..3499a3a5edde 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -1773,8 +1773,7 @@ public class AccountManagerService
// Create a Session for the target user and pass in the bundle
completeCloningAccount(response, result, account, toAccounts, userFrom);
} else {
- // Bundle format is not defined.
- super.onResultSkipSanitization(result);
+ super.onResult(result);
}
}
}.bind();
@@ -1861,8 +1860,7 @@ public class AccountManagerService
// account to avoid retries?
// TODO: what we do with the visibility?
- // Bundle format is not defined.
- super.onResultSkipSanitization(result);
+ super.onResult(result);
}
@Override
@@ -2108,7 +2106,6 @@ public class AccountManagerService
@Override
public void onResult(Bundle result) {
Bundle.setDefusable(result, true);
- result = sanitizeBundle(result);
IAccountManagerResponse response = getResponseAndClose();
if (response != null) {
try {
@@ -2462,7 +2459,6 @@ public class AccountManagerService
@Override
public void onResult(Bundle result) {
Bundle.setDefusable(result, true);
- result = sanitizeBundle(result);
if (result != null && result.containsKey(AccountManager.KEY_BOOLEAN_RESULT)
&& !result.containsKey(AccountManager.KEY_INTENT)) {
final boolean removalAllowed = result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT);
@@ -2977,7 +2973,6 @@ public class AccountManagerService
@Override
public void onResult(Bundle result) {
Bundle.setDefusable(result, true);
- result = sanitizeBundle(result);
if (result != null) {
String label = result.getString(AccountManager.KEY_AUTH_TOKEN_LABEL);
Bundle bundle = new Bundle();
@@ -3155,7 +3150,6 @@ public class AccountManagerService
@Override
public void onResult(Bundle result) {
Bundle.setDefusable(result, true);
- result = sanitizeBundle(result);
if (result != null) {
if (result.containsKey(AccountManager.KEY_AUTH_TOKEN_LABEL)) {
Intent intent = newGrantCredentialsPermissionIntent(
@@ -3627,12 +3621,6 @@ public class AccountManagerService
@Override
public void onResult(Bundle result) {
Bundle.setDefusable(result, true);
- Bundle sessionBundle = null;
- if (result != null) {
- // Session bundle will be removed from result.
- sessionBundle = result.getBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE);
- }
- result = sanitizeBundle(result);
mNumResults++;
Intent intent = null;
if (result != null) {
@@ -3694,6 +3682,7 @@ public class AccountManagerService
// bundle contains data necessary for finishing the session
// later. The session bundle will be encrypted here and
// decrypted later when trying to finish the session.
+ Bundle sessionBundle = result.getBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE);
if (sessionBundle != null) {
String accountType = sessionBundle.getString(AccountManager.KEY_ACCOUNT_TYPE);
if (TextUtils.isEmpty(accountType)
@@ -4081,7 +4070,6 @@ public class AccountManagerService
@Override
public void onResult(Bundle result) {
Bundle.setDefusable(result, true);
- result = sanitizeBundle(result);
IAccountManagerResponse response = getResponseAndClose();
if (response == null) {
return;
@@ -4395,7 +4383,6 @@ public class AccountManagerService
@Override
public void onResult(Bundle result) {
Bundle.setDefusable(result, true);
- result = sanitizeBundle(result);
mNumResults++;
if (result == null) {
onError(AccountManager.ERROR_CODE_INVALID_RESPONSE, "null bundle");
@@ -4952,68 +4939,6 @@ public class AccountManagerService
callback, resultReceiver);
}
-
- // All keys for Strings passed from AbstractAccountAuthenticator using Bundle.
- private static final String[] sStringBundleKeys = new String[] {
- AccountManager.KEY_ACCOUNT_NAME,
- AccountManager.KEY_ACCOUNT_TYPE,
- AccountManager.KEY_AUTHTOKEN,
- AccountManager.KEY_AUTH_TOKEN_LABEL,
- AccountManager.KEY_ERROR_MESSAGE,
- AccountManager.KEY_PASSWORD,
- AccountManager.KEY_ACCOUNT_STATUS_TOKEN};
-
- /**
- * Keep only documented fields in a Bundle received from AbstractAccountAuthenticator.
- */
- protected static Bundle sanitizeBundle(Bundle bundle) {
- if (bundle == null) {
- return null;
- }
- Bundle sanitizedBundle = new Bundle();
- Bundle.setDefusable(sanitizedBundle, true);
- int updatedKeysCount = 0;
- for (String stringKey : sStringBundleKeys) {
- if (bundle.containsKey(stringKey)) {
- String value = bundle.getString(stringKey);
- sanitizedBundle.putString(stringKey, value);
- updatedKeysCount++;
- }
- }
- String key = AbstractAccountAuthenticator.KEY_CUSTOM_TOKEN_EXPIRY;
- if (bundle.containsKey(key)) {
- long expiryMillis = bundle.getLong(key, 0L);
- sanitizedBundle.putLong(key, expiryMillis);
- updatedKeysCount++;
- }
- key = AccountManager.KEY_BOOLEAN_RESULT;
- if (bundle.containsKey(key)) {
- boolean booleanResult = bundle.getBoolean(key, false);
- sanitizedBundle.putBoolean(key, booleanResult);
- updatedKeysCount++;
- }
- key = AccountManager.KEY_ERROR_CODE;
- if (bundle.containsKey(key)) {
- int errorCode = bundle.getInt(key, 0);
- sanitizedBundle.putInt(key, errorCode);
- updatedKeysCount++;
- }
- key = AccountManager.KEY_INTENT;
- if (bundle.containsKey(key)) {
- Intent intent = bundle.getParcelable(key, Intent.class);
- sanitizedBundle.putParcelable(key, intent);
- updatedKeysCount++;
- }
- if (bundle.containsKey(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE)) {
- // The field is not copied in sanitized bundle.
- updatedKeysCount++;
- }
- if (updatedKeysCount != bundle.size()) {
- Log.w(TAG, "Size mismatch after sanitizeBundle call.");
- }
- return sanitizedBundle;
- }
-
private abstract class Session extends IAccountAuthenticatorResponse.Stub
implements IBinder.DeathRecipient, ServiceConnection {
private final Object mSessionLock = new Object();
@@ -5304,15 +5229,10 @@ public class AccountManagerService
}
}
}
+
@Override
public void onResult(Bundle result) {
Bundle.setDefusable(result, true);
- result = sanitizeBundle(result);
- onResultSkipSanitization(result);
- }
-
- public void onResultSkipSanitization(Bundle result) {
- Bundle.setDefusable(result, true);
mNumResults++;
Intent intent = null;
if (result != null) {
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 15277cebac6e..ef82c7477558 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -664,6 +664,8 @@ public final class BatteryStatsService extends IBatteryStats.Stub
} else if (nc.hasTransport(TRANSPORT_CELLULAR)) {
return CPU_WAKEUP_SUBSYSTEM_CELLULAR_DATA;
}
+ // For TRANSPORT_BLUETOOTH, we have a separate channel to catch Bluetooth wakeups.
+ // See noteCpuWakingSysproxyPacket method.
return CPU_WAKEUP_SUBSYSTEM_UNKNOWN;
}
@@ -686,6 +688,15 @@ public final class BatteryStatsService extends IBatteryStats.Stub
}
@Override
+ public void noteCpuWakingBluetoothProxyPacket(int uid, long elapsedMillis) {
+ if (uid < 0) {
+ Slog.e(TAG, "Invalid uid for waking bluetooth proxy packet: " + uid);
+ return;
+ }
+ noteCpuWakingActivity(CPU_WAKEUP_SUBSYSTEM_BLUETOOTH, elapsedMillis, uid);
+ }
+
+ @Override
public void noteBinderCallStats(int workSourceUid, long incrementatCallCount,
Collection<BinderCallsStats.CallStat> callStats) {
synchronized (BatteryStatsService.this.mLock) {
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index a13ce654bb95..bae9a670c438 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -40,6 +40,7 @@ import android.aconfigd.Aconfigd.StorageRequestMessages;
import android.aconfigd.Aconfigd.StorageReturnMessage;
import android.aconfigd.Aconfigd.StorageReturnMessages;
import static com.android.aconfig_new_storage.Flags.enableAconfigStorageDaemon;
+import static com.android.aconfig_new_storage.Flags.supportImmediateLocalOverrides;
import java.io.DataInputStream;
import java.io.DataOutputStream;
@@ -491,14 +492,18 @@ public class SettingsToPropertiesMapper {
static void writeFlagOverrideRequest(
ProtoOutputStream proto, String packageName, String flagName, String flagValue,
boolean isLocal) {
+ int localOverrideTag = supportImmediateLocalOverrides()
+ ? StorageRequestMessage.LOCAL_IMMEDIATE
+ : StorageRequestMessage.LOCAL_ON_REBOOT;
+
long msgsToken = proto.start(StorageRequestMessages.MSGS);
long msgToken = proto.start(StorageRequestMessage.FLAG_OVERRIDE_MESSAGE);
proto.write(StorageRequestMessage.FlagOverrideMessage.PACKAGE_NAME, packageName);
proto.write(StorageRequestMessage.FlagOverrideMessage.FLAG_NAME, flagName);
proto.write(StorageRequestMessage.FlagOverrideMessage.FLAG_VALUE, flagValue);
proto.write(StorageRequestMessage.FlagOverrideMessage.OVERRIDE_TYPE, isLocal
- ? StorageRequestMessage.LOCAL_ON_REBOOT
- : StorageRequestMessage.SERVER_ON_REBOOT);
+ ? localOverrideTag
+ : StorageRequestMessage.SERVER_ON_REBOOT);
proto.end(msgToken);
proto.end(msgsToken);
}
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index 9b51b6ae4b0f..4f6da3baca12 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -205,4 +205,12 @@ flag {
metadata {
purpose: PURPOSE_BUGFIX
}
-} \ No newline at end of file
+}
+
+flag {
+ name: "defer_display_events_when_frozen"
+ namespace: "system_performance"
+ is_fixed_read_only: true
+ description: "Defer submitting display events to frozen processes."
+ bug: "326315985"
+}
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index a6389f7f5311..e0cf96fbccd0 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -1031,6 +1031,9 @@ public class AppOpsService extends IAppOpsService.Stub {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
+ if (action == null) {
+ return;
+ }
String pkgName = intent.getData().getEncodedSchemeSpecificPart().intern();
int uid = intent.getIntExtra(Intent.EXTRA_UID, Process.INVALID_UID);
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index feef5409d14f..4c917894100a 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -725,7 +725,7 @@ public class BiometricService extends SystemService {
return -1;
}
- if (!Utils.isValidAuthenticatorConfig(promptInfo)) {
+ if (!Utils.isValidAuthenticatorConfig(getContext(), promptInfo)) {
throw new SecurityException("Invalid authenticator configuration");
}
@@ -763,7 +763,7 @@ public class BiometricService extends SystemService {
+ ", Caller=" + callingUserId
+ ", Authenticators=" + authenticators);
- if (!Utils.isValidAuthenticatorConfig(authenticators)) {
+ if (!Utils.isValidAuthenticatorConfig(getContext(), authenticators)) {
throw new SecurityException("Invalid authenticator configuration");
}
@@ -1038,7 +1038,7 @@ public class BiometricService extends SystemService {
+ ", Caller=" + callingUserId
+ ", Authenticators=" + authenticators);
- if (!Utils.isValidAuthenticatorConfig(authenticators)) {
+ if (!Utils.isValidAuthenticatorConfig(getContext(), authenticators)) {
throw new SecurityException("Invalid authenticator configuration");
}
@@ -1060,7 +1060,7 @@ public class BiometricService extends SystemService {
Slog.d(TAG, "getSupportedModalities: Authenticators=" + authenticators);
- if (!Utils.isValidAuthenticatorConfig(authenticators)) {
+ if (!Utils.isValidAuthenticatorConfig(getContext(), authenticators)) {
throw new SecurityException("Invalid authenticator configuration");
}
diff --git a/services/core/java/com/android/server/biometrics/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java
index 407ef1e41aa6..de7bce78587b 100644
--- a/services/core/java/com/android/server/biometrics/Utils.java
+++ b/services/core/java/com/android/server/biometrics/Utils.java
@@ -16,6 +16,7 @@
package com.android.server.biometrics;
+import static android.Manifest.permission.SET_BIOMETRIC_DIALOG_ADVANCED;
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
import static android.hardware.biometrics.BiometricManager.Authenticators;
@@ -233,17 +234,18 @@ public class Utils {
* @param promptInfo
* @return
*/
- static boolean isValidAuthenticatorConfig(PromptInfo promptInfo) {
+ static boolean isValidAuthenticatorConfig(Context context, PromptInfo promptInfo) {
final int authenticators = promptInfo.getAuthenticators();
- return isValidAuthenticatorConfig(authenticators);
+ return isValidAuthenticatorConfig(context, authenticators);
}
/**
- * Checks if the authenticator configuration is a valid combination of the public APIs
- * @param authenticators
- * @return
+ * Checks if the authenticator configuration is a valid combination of the public APIs.
+ *
+ * throws {@link SecurityException} if the caller requests for mandatory biometrics without
+ * {@link SET_BIOMETRIC_DIALOG_ADVANCED} permission
*/
- static boolean isValidAuthenticatorConfig(int authenticators) {
+ static boolean isValidAuthenticatorConfig(Context context, int authenticators) {
// The caller is not required to set the authenticators. But if they do, check the below.
if (authenticators == 0) {
return true;
@@ -271,6 +273,9 @@ public class Utils {
} else if (biometricBits == Authenticators.BIOMETRIC_WEAK) {
return true;
} else if (isMandatoryBiometricsRequested(authenticators)) {
+ //TODO(b/347123256): Update CTS test
+ context.enforceCallingOrSelfPermission(SET_BIOMETRIC_DIALOG_ADVANCED,
+ "Must have SET_BIOMETRIC_DIALOG_ADVANCED permission");
return true;
}
diff --git a/services/core/java/com/android/server/crashrecovery/CrashRecoveryModule.java b/services/core/java/com/android/server/crashrecovery/CrashRecoveryModule.java
index 5f2fbcedce88..8a81aaa1e636 100644
--- a/services/core/java/com/android/server/crashrecovery/CrashRecoveryModule.java
+++ b/services/core/java/com/android/server/crashrecovery/CrashRecoveryModule.java
@@ -23,7 +23,10 @@ import com.android.server.RescueParty;
import com.android.server.SystemService;
-/** This class encapsulate the lifecycle methods of CrashRecovery module. */
+/** This class encapsulate the lifecycle methods of CrashRecovery module.
+ *
+ * @hide
+ */
public class CrashRecoveryModule {
private static final String TAG = "CrashRecoveryModule";
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 88907e35854f..67e2ca2b312c 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -48,6 +48,7 @@ import static android.os.Process.ROOT_UID;
import static android.provider.Settings.Secure.RESOLUTION_MODE_FULL;
import static android.provider.Settings.Secure.RESOLUTION_MODE_HIGH;
import static android.provider.Settings.Secure.RESOLUTION_MODE_UNKNOWN;
+import static android.text.TextUtils.formatSimple;
import static android.view.Display.HdrCapabilities.HDR_TYPE_INVALID;
import static com.android.server.display.layout.Layout.Display.POSITION_REAR;
@@ -279,6 +280,8 @@ public final class DisplayManagerService extends SystemService {
private InputManagerInternal mInputManagerInternal;
private ActivityManagerInternal mActivityManagerInternal;
private final UidImportanceListener mUidImportanceListener = new UidImportanceListener();
+ private final DisplayFrozenProcessListener mDisplayFrozenProcessListener;
+
@Nullable
private IMediaProjectionManager mProjectionService;
private DeviceStateManagerInternal mDeviceStateManager;
@@ -321,6 +324,12 @@ public final class DisplayManagerService extends SystemService {
@GuardedBy("mSyncRoot")
private final SparseArray<CallbackRecord> mCallbacks = new SparseArray<>();
+ // All callback records indexed by [uid][pid], for fast lookup by uid.
+ // This is only used if {@link deferDisplayEventsWhenFrozen()} is true.
+ @GuardedBy("mSyncRoot")
+ private final SparseArray<SparseArray<CallbackRecord>> mCallbackRecordByPidByUid =
+ new SparseArray<>();
+
/**
* All {@link IVirtualDevice} and {@link DisplayWindowPolicyController}s indexed by
* {@link DisplayInfo#displayId}.
@@ -472,6 +481,7 @@ public final class DisplayManagerService extends SystemService {
// Pending callback records indexed by calling process uid and pid.
// Must be used outside of the lock mSyncRoot and should be self-locked.
+ // This is only used when {@link deferDisplayEventsWhenFrozen()} is false.
@GuardedBy("mPendingCallbackSelfLocked")
private final SparseArray<SparseArray<PendingCallback>> mPendingCallbackSelfLocked =
new SparseArray<>();
@@ -611,6 +621,7 @@ public final class DisplayManagerService extends SystemService {
mFlags = injector.getFlags();
mHandler = new DisplayManagerHandler(displayThreadLooper);
mHandlerExecutor = new HandlerExecutor(mHandler);
+ mDisplayFrozenProcessListener = new DisplayFrozenProcessListener();
mUiHandler = UiThread.getHandler();
mDisplayDeviceRepo = new DisplayDeviceRepository(mSyncRoot, mPersistentDataStore);
mLogicalDisplayMapper = new LogicalDisplayMapper(mContext,
@@ -1034,10 +1045,14 @@ public final class DisplayManagerService extends SystemService {
private class UidImportanceListener implements ActivityManager.OnUidImportanceListener {
@Override
public void onUidImportance(int uid, int importance) {
- onUidImportanceInternal(uid, importance);
+ if (deferDisplayEventsWhenFrozen()) {
+ onUidImportanceFlagged(uid, importance);
+ } else {
+ onUidImportanceUnflagged(uid, importance);
+ }
}
- private void onUidImportanceInternal(int uid, int importance) {
+ private void onUidImportanceUnflagged(int uid, int importance) {
synchronized (mPendingCallbackSelfLocked) {
if (importance >= IMPORTANCE_GONE) {
// Clean up as the app is already gone
@@ -1068,6 +1083,83 @@ public final class DisplayManagerService extends SystemService {
mPendingCallbackSelfLocked.delete(uid);
}
}
+
+ private void onUidImportanceFlagged(int uid, int importance) {
+ final boolean cached = (importance >= IMPORTANCE_CACHED);
+ List<CallbackRecord> readyCallbackRecords = null;
+ synchronized (mSyncRoot) {
+ final SparseArray<CallbackRecord> procs = mCallbackRecordByPidByUid.get(uid);
+ if (procs == null) {
+ return;
+ }
+ if (cached) {
+ setCachedLocked(procs);
+ } else {
+ readyCallbackRecords = setUncachedLocked(procs);
+ }
+ }
+ if (readyCallbackRecords != null) {
+ // Attempt to dispatch pending events if the UID is coming out of cached state.
+ for (int i = 0; i < readyCallbackRecords.size(); i++) {
+ readyCallbackRecords.get(i).dispatchPending();
+ }
+ }
+ }
+
+ // Set all processes in the list to cached.
+ @GuardedBy("mSyncRoot")
+ private void setCachedLocked(SparseArray<CallbackRecord> procs) {
+ for (int i = 0; i < procs.size(); i++) {
+ final CallbackRecord cb = procs.valueAt(i);
+ if (cb != null) {
+ cb.setCached(true);
+ }
+ }
+ }
+
+ // Set all processes to uncached and return the list of processes that were modified.
+ @GuardedBy("mSyncRoot")
+ private List<CallbackRecord> setUncachedLocked(SparseArray<CallbackRecord> procs) {
+ ArrayList<CallbackRecord> ready = null;
+ for (int i = 0; i < procs.size(); i++) {
+ final CallbackRecord cb = procs.valueAt(i);
+ if (cb != null) {
+ if (cb.setCached(false)) {
+ if (ready == null) ready = new ArrayList<>();
+ ready.add(cb);
+ }
+ }
+ }
+ return ready;
+ }
+ }
+
+ private class DisplayFrozenProcessListener
+ implements ActivityManagerInternal.FrozenProcessListener {
+ public void onProcessFrozen(int pid) {
+ synchronized (mSyncRoot) {
+ CallbackRecord callback = mCallbacks.get(pid);
+ if (callback == null) {
+ return;
+ }
+ callback.setFrozen(true);
+ }
+ }
+
+ public void onProcessUnfrozen(int pid) {
+ // First, see if there is a callback associated with this pid. If there's no
+ // callback, then there is nothing to do.
+ CallbackRecord callback;
+ synchronized (mSyncRoot) {
+ callback = mCallbacks.get(pid);
+ if (callback == null) {
+ return;
+ }
+ callback.setFrozen(false);
+ }
+ // Attempt to dispatch pending events if the process is coming out of frozen.
+ callback.dispatchPending();
+ }
}
private class SettingsObserver extends ContentObserver {
@@ -1314,12 +1406,29 @@ public final class DisplayManagerService extends SystemService {
}
mCallbacks.put(callingPid, record);
+ if (deferDisplayEventsWhenFrozen()) {
+ SparseArray<CallbackRecord> uidPeers = mCallbackRecordByPidByUid.get(record.mUid);
+ if (uidPeers == null) {
+ uidPeers = new SparseArray<CallbackRecord>();
+ mCallbackRecordByPidByUid.put(record.mUid, uidPeers);
+ }
+ uidPeers.put(record.mPid, record);
+ }
}
}
private void onCallbackDied(CallbackRecord record) {
synchronized (mSyncRoot) {
mCallbacks.remove(record.mPid);
+ if (deferDisplayEventsWhenFrozen()) {
+ SparseArray<CallbackRecord> uidPeers = mCallbackRecordByPidByUid.get(record.mUid);
+ if (uidPeers != null) {
+ uidPeers.remove(record.mPid);
+ if (uidPeers.size() == 0) {
+ mCallbackRecordByPidByUid.remove(record.mUid);
+ }
+ }
+ }
stopWifiDisplayScanLocked(record);
}
}
@@ -3296,12 +3405,16 @@ public final class DisplayManagerService extends SystemService {
// After releasing the lock, send the notifications out.
for (int i = 0; i < mTempCallbacks.size(); i++) {
CallbackRecord callbackRecord = mTempCallbacks.get(i);
- deliverEventInternal(callbackRecord, displayId, event);
+ if (deferDisplayEventsWhenFrozen()) {
+ deliverEventFlagged(callbackRecord, displayId, event);
+ } else {
+ deliverEventUnflagged(callbackRecord, displayId, event);
+ }
}
mTempCallbacks.clear();
}
- private void deliverEventInternal(CallbackRecord callbackRecord, int displayId, int event) {
+ private void deliverEventUnflagged(CallbackRecord callbackRecord, int displayId, int event) {
final int uid = callbackRecord.mUid;
final int pid = callbackRecord.mPid;
if (isUidCached(uid)) {
@@ -3330,6 +3443,10 @@ public final class DisplayManagerService extends SystemService {
}
}
+ private void deliverEventFlagged(CallbackRecord callbackRecord, int displayId, int event) {
+ callbackRecord.notifyDisplayEventAsync(displayId, event);
+ }
+
private boolean extraLogging(String packageName) {
return mExtraDisplayEventLogging && mExtraDisplayLoggingPackageName.equals(packageName);
}
@@ -3377,10 +3494,18 @@ public final class DisplayManagerService extends SystemService {
private void dumpInternal(PrintWriter pw) {
pw.println("DISPLAY MANAGER (dumpsys display)");
BrightnessTracker brightnessTrackerLocal;
+ SparseArray<DisplayPowerController> displayPowerControllersLocal = new SparseArray<>();
+ int displayPowerControllerCount;
synchronized (mSyncRoot) {
brightnessTrackerLocal = mBrightnessTracker;
+ displayPowerControllerCount = mDisplayPowerControllers.size();
+ for (int i = 0; i < displayPowerControllerCount; i++) {
+ displayPowerControllersLocal.put(
+ mDisplayPowerControllers.keyAt(i), mDisplayPowerControllers.valueAt(i));
+ }
+
pw.println(" mSafeMode=" + mSafeMode);
pw.println(" mPendingTraversal=" + mPendingTraversal);
pw.println(" mViewports=" + mViewports);
@@ -3446,16 +3571,7 @@ public final class DisplayManagerService extends SystemService {
pw.println("Callbacks: size=" + callbackCount);
pw.println("-----------------");
for (int i = 0; i < callbackCount; i++) {
- CallbackRecord callback = mCallbacks.valueAt(i);
- pw.println(" " + i + ": mPid=" + callback.mPid
- + ", mWifiDisplayScanRequested=" + callback.mWifiDisplayScanRequested);
- }
-
- final int displayPowerControllerCount = mDisplayPowerControllers.size();
- pw.println();
- pw.println("Display Power Controllers: size=" + displayPowerControllerCount);
- for (int i = 0; i < displayPowerControllerCount; i++) {
- mDisplayPowerControllers.valueAt(i).dump(pw);
+ pw.println(" " + i + ": " + mCallbacks.valueAt(i).dump());
}
pw.println();
@@ -3470,6 +3586,12 @@ public final class DisplayManagerService extends SystemService {
mDisplayWindowPolicyControllers.valueAt(i).second.dump(" ", pw);
}
}
+ pw.println();
+ pw.println("Display Power Controllers: size=" + displayPowerControllerCount);
+ for (int i = 0; i < displayPowerControllerCount; i++) {
+ displayPowerControllersLocal.valueAt(i).dump(pw);
+ }
+
if (brightnessTrackerLocal != null) {
pw.println();
brightnessTrackerLocal.dump(pw);
@@ -3848,12 +3970,43 @@ public final class DisplayManagerService extends SystemService {
public boolean mWifiDisplayScanRequested;
+ // A single pending event.
+ private record Event(int displayId, @DisplayEvent int event) { };
+
+ // The list of pending events. This is null until there is a pending event to be saved.
+ // This is only used if {@link deferDisplayEventsWhenFrozen()} is true.
+ @GuardedBy("mCallback")
+ private ArrayList<Event> mPendingEvents;
+
+ // Process states: a process is ready to receive events if it is neither cached nor
+ // frozen.
+ @GuardedBy("mCallback")
+ private boolean mCached;
+ @GuardedBy("mCallback")
+ private boolean mFrozen;
+
CallbackRecord(int pid, int uid, @NonNull IDisplayManagerCallback callback,
@EventsMask long eventsMask) {
mPid = pid;
mUid = uid;
mCallback = callback;
mEventsMask = new AtomicLong(eventsMask);
+ mCached = false;
+ mFrozen = false;
+
+ if (deferDisplayEventsWhenFrozen()) {
+ // Some CallbackRecords are registered very early in system boot, before
+ // mActivityManagerInternal is initialized. If mActivityManagerInternal is null,
+ // do not register the frozen process listener. However, do verify that all such
+ // registrations are for the self pid (which can never be frozen, so the frozen
+ // process listener does not matter).
+ if (mActivityManagerInternal != null) {
+ mActivityManagerInternal.addFrozenProcessListener(pid, mHandlerExecutor,
+ mDisplayFrozenProcessListener);
+ } else if (Process.myPid() != pid) {
+ Slog.e(TAG, "DisplayListener registered too early");
+ }
+ }
String[] packageNames = mContext.getPackageManager().getPackagesForUid(uid);
mPackageName = packageNames == null ? null : packageNames[0];
@@ -3863,6 +4016,46 @@ public final class DisplayManagerService extends SystemService {
mEventsMask.set(eventsMask);
}
+ /**
+ * Return true if the process can accept events.
+ */
+ @GuardedBy("mCallback")
+ private boolean isReadyLocked() {
+ return !mCached && !mFrozen;
+ }
+
+ /**
+ * Return true if the process is now ready and has pending events to be delivered.
+ */
+ @GuardedBy("mCallback")
+ private boolean hasPendingAndIsReadyLocked() {
+ return isReadyLocked() && mPendingEvents != null && !mPendingEvents.isEmpty();
+ }
+
+ /**
+ * Set the frozen flag for this process. Return true if the process is now ready to
+ * receive events and there are pending events to be delivered.
+ * This is only used if {@link deferDisplayEventsWhenFrozen()} is true.
+ */
+ public boolean setFrozen(boolean frozen) {
+ synchronized (mCallback) {
+ mFrozen = frozen;
+ return hasPendingAndIsReadyLocked();
+ }
+ }
+
+ /**
+ * Set the cached flag for this process. Return true if the process is now ready to
+ * receive events and there are pending events to be delivered.
+ * This is only used if {@link deferDisplayEventsWhenFrozen()} is true.
+ */
+ public boolean setCached(boolean cached) {
+ synchronized (mCallback) {
+ mCached = cached;
+ return hasPendingAndIsReadyLocked();
+ }
+ }
+
@Override
public void binderDied() {
if (DEBUG || extraLogging(mPackageName)) {
@@ -3878,7 +4071,7 @@ public final class DisplayManagerService extends SystemService {
/**
* @return {@code false} if RemoteException happens; otherwise {@code true} for
* success. This returns true even if the event was deferred because the remote client is
- * cached.
+ * cached or frozen.
*/
public boolean notifyDisplayEventAsync(int displayId, @DisplayEvent int event) {
if (!shouldSendEvent(event)) {
@@ -3895,6 +4088,22 @@ public final class DisplayManagerService extends SystemService {
return true;
}
+ if (deferDisplayEventsWhenFrozen()) {
+ synchronized (mCallback) {
+ // Add the new event to the pending list if the client frozen or cached (not
+ // ready) or if there are existing pending events. The latter condition
+ // occurs as the client is transitioning to ready but pending events have not
+ // been dispatched. The new event must be added to the pending list to
+ // preserve event ordering.
+ if (!isReadyLocked() || (mPendingEvents != null && !mPendingEvents.isEmpty())) {
+ // The client is interested in the event but is not ready to receive it.
+ // Put the event on the pending list.
+ addDisplayEvent(displayId, event);
+ return true;
+ }
+ }
+ }
+
return transmitDisplayEvent(displayId, event);
}
@@ -3941,8 +4150,81 @@ public final class DisplayManagerService extends SystemService {
return true;
}
}
+
+ // Add a single event to the pending list, possibly combining or collapsing events in the
+ // list.
+ // This is only used if {@link deferDisplayEventsWhenFrozen()} is true.
+ @GuardedBy("mCallback")
+ private void addDisplayEvent(int displayId, int event) {
+ if (mPendingEvents == null) {
+ mPendingEvents = new ArrayList<>();
+ }
+ if (!mPendingEvents.isEmpty()) {
+ // Ignore redundant events. Further optimization is possible by merging adjacent
+ // events.
+ Event last = mPendingEvents.get(mPendingEvents.size() - 1);
+ if (last.displayId == displayId && last.event == event) {
+ if (DEBUG) {
+ Slog.d(TAG, "Ignore redundant display event " + displayId + "/" + event
+ + " to " + mUid + "/" + mPid);
+ }
+ return;
+ }
+ }
+ mPendingEvents.add(new Event(displayId, event));
+ }
+
+ // Send all pending events. This can safely be called if the process is not ready, but it
+ // would be unusual to do so. The method returns true on success.
+ // This is only used if {@link deferDisplayEventsWhenFrozen()} is true.
+ public boolean dispatchPending() {
+ synchronized (mCallback) {
+ if (mPendingEvents == null || mPendingEvents.isEmpty()) {
+ return true;
+ }
+ if (!isReadyLocked()) {
+ return false;
+ }
+ for (int i = 0; i < mPendingEvents.size(); i++) {
+ Event displayEvent = mPendingEvents.get(i);
+ if (DEBUG) {
+ Slog.d(TAG, "Send pending display event #" + i + " "
+ + displayEvent.displayId + "/"
+ + displayEvent.event + " to " + mUid + "/" + mPid);
+ }
+ if (!transmitDisplayEvent(displayEvent.displayId, displayEvent.event)) {
+ Slog.d(TAG, "Drop pending events for dead process " + mPid);
+ break;
+ }
+ }
+ mPendingEvents.clear();
+ return true;
+ }
+ }
+
+ // Return a string suitable for dumpsys.
+ private String dump() {
+ if (deferDisplayEventsWhenFrozen()) {
+ final String fmt =
+ "mPid=%d mUid=%d mWifiDisplayScanRequested=%s"
+ + " cached=%s frozen=%s pending=%d";
+ synchronized (mCallback) {
+ return formatSimple(fmt,
+ mPid, mUid, mWifiDisplayScanRequested, mCached, mFrozen,
+ (mPendingEvents == null) ? 0 : mPendingEvents.size());
+ }
+ } else {
+ final String fmt =
+ "mPid=%d mUid=%d mWifiDisplayScanRequested=%s";
+ return formatSimple(fmt,
+ mPid, mUid, mWifiDisplayScanRequested);
+ }
+ }
}
+ /**
+ * This is only used if {@link deferDisplayEventsWhenFrozen()} is false.
+ */
private static final class PendingCallback {
private final CallbackRecord mCallbackRecord;
private final ArrayList<Pair<Integer, Integer>> mDisplayEvents;
@@ -5497,4 +5779,11 @@ public final class DisplayManagerService extends SystemService {
return mExternalDisplayStatsService;
}
}
+
+ /**
+ * Return the value of the pause
+ */
+ private static boolean deferDisplayEventsWhenFrozen() {
+ return com.android.server.am.Flags.deferDisplayEventsWhenFrozen();
+ }
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 62d876102720..03fec0115613 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1480,29 +1480,24 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
brightnessState = clampScreenBrightness(brightnessState);
}
- if (useDozeBrightness) {
- // TODO(b/329676661): Introduce a config property to choose between this brightness
- // strategy and DOZE_DEFAULT
- // On some devices, when auto-brightness is disabled and the device is dozing, we use
- // the current brightness setting scaled by the doze scale factor
- if ((Float.isNaN(brightnessState)
- || displayBrightnessState.getDisplayBrightnessStrategyName()
- .equals(DisplayBrightnessStrategyConstants.FALLBACK_BRIGHTNESS_STRATEGY_NAME))
- && mFlags.isDisplayOffloadEnabled()
- && mDisplayOffloadSession != null
+ if (useDozeBrightness && (Float.isNaN(brightnessState)
+ || displayBrightnessState.getDisplayBrightnessStrategyName()
+ .equals(DisplayBrightnessStrategyConstants.FALLBACK_BRIGHTNESS_STRATEGY_NAME))) {
+ if (mFlags.isDisplayOffloadEnabled() && mDisplayOffloadSession != null
&& (mAutomaticBrightnessController == null
|| !mAutomaticBrightnessStrategy.shouldUseAutoBrightness())) {
+ // TODO(b/329676661): Introduce a config property to choose between this brightness
+ // strategy and DOZE_DEFAULT
+ // On some devices, when auto-brightness is disabled and the device is dozing, we
+ // use the current brightness setting scaled by the doze scale factor
rawBrightnessState = getDozeBrightnessForOffload();
brightnessState = clampScreenBrightness(rawBrightnessState);
updateScreenBrightnessSetting = false;
mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE_MANUAL);
mTempBrightnessEvent.setFlags(
mTempBrightnessEvent.getFlags() | BrightnessEvent.FLAG_DOZE_SCALE);
- }
-
- // Use default brightness when dozing unless overridden.
- if (Float.isNaN(brightnessState)
- && !mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig()) {
+ } else if (!mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig()) {
+ // Use default brightness when dozing unless overridden.
rawBrightnessState = mScreenBrightnessDozeConfig;
brightnessState = clampScreenBrightness(rawBrightnessState);
mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE_DEFAULT);
diff --git a/services/core/java/com/android/server/flags/pinner.aconfig b/services/core/java/com/android/server/flags/pinner.aconfig
index 2f817dbb9a7f..345366882d5a 100644
--- a/services/core/java/com/android/server/flags/pinner.aconfig
+++ b/services/core/java/com/android/server/flags/pinner.aconfig
@@ -9,8 +9,11 @@ flag {
}
flag {
- name: "skip_home_art_pins"
- namespace: "system_performance"
- description: "Ablation study flag that controls if home app odex/vdex files should be pinned in memory."
- bug: "340935152"
-} \ No newline at end of file
+ name: "pin_global_quota"
+ namespace: "system_performance"
+ description: "This flag controls whether pinner will use a global quota or not"
+ bug: "340935152"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/services/core/java/com/android/server/inputmethod/HardwareKeyboardShortcutController.java b/services/core/java/com/android/server/inputmethod/HardwareKeyboardShortcutController.java
index 41313fa1fb2c..ef1220fb1786 100644
--- a/services/core/java/com/android/server/inputmethod/HardwareKeyboardShortcutController.java
+++ b/services/core/java/com/android/server/inputmethod/HardwareKeyboardShortcutController.java
@@ -33,9 +33,6 @@ final class HardwareKeyboardShortcutController {
@GuardedBy("ImfLock.class")
private final ArrayList<InputMethodSubtypeHandle> mSubtypeHandles = new ArrayList<>();
- HardwareKeyboardShortcutController() {
- }
-
@GuardedBy("ImfLock.class")
void update(@NonNull InputMethodSettings settings) {
mSubtypeHandles.clear();
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodDeviceConfigs.java b/services/core/java/com/android/server/inputmethod/InputMethodDeviceConfigs.java
index 6cd2493cfdff..fc4c0fc798db 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodDeviceConfigs.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodDeviceConfigs.java
@@ -40,6 +40,7 @@ final class InputMethodDeviceConfigs {
if (KEY_HIDE_IME_WHEN_NO_EDITOR_FOCUS.equals(name)) {
mHideImeWhenNoEditorFocus = properties.getBoolean(name,
true /* defaultValue */);
+ break;
}
}
};
diff --git a/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
index 214aa1d904fa..49d4332d9e2a 100644
--- a/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
+++ b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
@@ -394,6 +394,7 @@ final class ZeroJankProxy implements IInputMethodManagerImpl.Callback {
flags),
this::offload).get();
} catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e);
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index 48d24f2e14dd..47f579db604f 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -195,10 +195,9 @@ public final class MediaProjectionManagerService extends SystemService
== PackageManager.PERMISSION_GRANTED) {
return true;
}
- boolean operationActive = mAppOps.isOperationActive(AppOpsManager.OP_PROJECT_MEDIA,
- mProjectionGrant.uid,
- mProjectionGrant.packageName);
- if (operationActive) {
+ if (AppOpsManager.MODE_ALLOWED == mAppOps.noteOpNoThrow(AppOpsManager.OP_PROJECT_MEDIA,
+ mProjectionGrant.uid, mProjectionGrant.packageName, /* attributionTag= */ null,
+ "recording lockscreen")) {
// Some tools use media projection by granting the OP_PROJECT_MEDIA app
// op via a shell command. Those tools can be granted keyguard capture
return true;
diff --git a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
index abb21323f7f0..06f419a785f9 100644
--- a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
+++ b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
@@ -118,6 +118,37 @@ public final class NotificationAttentionHelper {
Intent.ACTION_MANAGED_PROFILE_AVAILABLE, new Pair<>(Intent.EXTRA_QUIET_MODE, false)
);
+ // Bits 1, 2, 3, 4 are already taken by: beep|buzz|blink|cooldown
+ static final int MUTE_REASON_NOT_MUTED = 0;
+ static final int MUTE_REASON_NOT_AUDIBLE = 1 << 5;
+ static final int MUTE_REASON_SILENT_UPDATE = 1 << 6;
+ static final int MUTE_REASON_POST_SILENTLY = 1 << 7;
+ static final int MUTE_REASON_LISTENER_HINT = 1 << 8;
+ static final int MUTE_REASON_DND = 1 << 9;
+ static final int MUTE_REASON_GROUP_ALERT = 1 << 10;
+ static final int MUTE_REASON_FLAG_SILENT = 1 << 11;
+ static final int MUTE_REASON_RATE_LIMIT = 1 << 12;
+ static final int MUTE_REASON_OTHER_INSISTENT_PLAYING = 1 << 13;
+ static final int MUTE_REASON_SUPPRESSED_BUBBLE = 1 << 14;
+ static final int MUTE_REASON_COOLDOWN = 1 << 15;
+
+ @IntDef(prefix = { "MUTE_REASON_" }, value = {
+ MUTE_REASON_NOT_MUTED,
+ MUTE_REASON_NOT_AUDIBLE,
+ MUTE_REASON_SILENT_UPDATE,
+ MUTE_REASON_POST_SILENTLY,
+ MUTE_REASON_LISTENER_HINT,
+ MUTE_REASON_DND,
+ MUTE_REASON_GROUP_ALERT,
+ MUTE_REASON_FLAG_SILENT,
+ MUTE_REASON_RATE_LIMIT,
+ MUTE_REASON_OTHER_INSISTENT_PLAYING,
+ MUTE_REASON_SUPPRESSED_BUBBLE,
+ MUTE_REASON_COOLDOWN,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface MuteReason {}
+
private final Context mContext;
private final PackageManager mPackageManager;
private final TelephonyManager mTelephonyManager;
@@ -388,6 +419,7 @@ public final class NotificationAttentionHelper {
boolean buzz = false;
boolean beep = false;
boolean blink = false;
+ @MuteReason int shouldMuteReason = MUTE_REASON_NOT_MUTED;
final String key = record.getKey();
@@ -395,10 +427,6 @@ public final class NotificationAttentionHelper {
Log.d(TAG, "buzzBeepBlinkLocked " + record);
}
- if (isPoliteNotificationFeatureEnabled(record)) {
- mStrategy.onNotificationPosted(record);
- }
-
// Should this notification make noise, vibe, or use the LED?
final boolean aboveThreshold =
mIsAutomotive
@@ -443,7 +471,8 @@ public final class NotificationAttentionHelper {
boolean vibrateOnly =
hasValidVibrate && mNotificationCooldownVibrateUnlocked && mUserPresent;
boolean hasAudibleAlert = hasValidSound || hasValidVibrate;
- if (hasAudibleAlert && !shouldMuteNotificationLocked(record, signals)) {
+ shouldMuteReason = shouldMuteNotificationLocked(record, signals, hasAudibleAlert);
+ if (shouldMuteReason == MUTE_REASON_NOT_MUTED) {
if (!sentAccessibilityEvent) {
sendAccessibilityEvent(record);
sentAccessibilityEvent = true;
@@ -541,15 +570,17 @@ public final class NotificationAttentionHelper {
}
}
final int buzzBeepBlinkLoggingCode =
- (buzz ? 1 : 0) | (beep ? 2 : 0) | (blink ? 4 : 0) | getPoliteBit(record);
+ (buzz ? 1 : 0) | (beep ? 2 : 0) | (blink ? 4 : 0)
+ | getPoliteBit(record) | shouldMuteReason;
if (buzzBeepBlinkLoggingCode > 0) {
MetricsLogger.action(record.getLogMaker()
.setCategory(MetricsEvent.NOTIFICATION_ALERT)
.setType(MetricsEvent.TYPE_OPEN)
.setSubtype(buzzBeepBlinkLoggingCode));
EventLogTags.writeNotificationAlert(key, buzz ? 1 : 0, beep ? 1 : 0, blink ? 1 : 0,
- getPolitenessState(record));
+ getPolitenessState(record), shouldMuteReason);
}
+
if (Flags.politeNotifications()) {
// Update last alert time
if (buzz || beep) {
@@ -594,41 +625,46 @@ public final class NotificationAttentionHelper {
mNMP.getNotificationByKey(mVibrateNotificationKey));
}
- boolean shouldMuteNotificationLocked(final NotificationRecord record, final Signals signals) {
+ @MuteReason int shouldMuteNotificationLocked(final NotificationRecord record,
+ final Signals signals, boolean hasAudibleAlert) {
+ // Suppressed because no audible alert
+ if (!hasAudibleAlert) {
+ return MUTE_REASON_NOT_AUDIBLE;
+ }
// Suppressed because it's a silent update
final Notification notification = record.getNotification();
if (record.isUpdate && (notification.flags & FLAG_ONLY_ALERT_ONCE) != 0) {
- return true;
+ return MUTE_REASON_SILENT_UPDATE;
}
// Suppressed because a user manually unsnoozed something (or similar)
if (record.shouldPostSilently()) {
- return true;
+ return MUTE_REASON_POST_SILENTLY;
}
// muted by listener
final String disableEffects = disableNotificationEffects(record, signals.listenerHints);
if (disableEffects != null) {
ZenLog.traceDisableEffects(record, disableEffects);
- return true;
+ return MUTE_REASON_LISTENER_HINT;
}
// suppressed due to DND
if (record.isIntercepted()) {
- return true;
+ return MUTE_REASON_DND;
}
// Suppressed because another notification in its group handles alerting
if (record.getSbn().isGroup()) {
if (notification.suppressAlertingDueToGrouping()) {
- return true;
+ return MUTE_REASON_GROUP_ALERT;
}
}
// Suppressed because notification was explicitly flagged as silent
if (android.service.notification.Flags.notificationSilentFlag()) {
if (notification.isSilent()) {
- return true;
+ return MUTE_REASON_FLAG_SILENT;
}
}
@@ -636,12 +672,12 @@ public final class NotificationAttentionHelper {
final String pkg = record.getSbn().getPackageName();
if (mUsageStats.isAlertRateLimited(pkg)) {
Slog.e(TAG, "Muting recently noisy " + record.getKey());
- return true;
+ return MUTE_REASON_RATE_LIMIT;
}
// A different looping ringtone, such as an incoming call is playing
if (isCurrentlyInsistent() && !isInsistentUpdate(record)) {
- return true;
+ return MUTE_REASON_OTHER_INSISTENT_PLAYING;
}
// Suppressed since it's a non-interruptive update to a bubble-suppressed notification
@@ -650,11 +686,23 @@ public final class NotificationAttentionHelper {
if (record.isUpdate && !record.isInterruptive() && isBubbleOrOverflowed
&& record.getNotification().getBubbleMetadata() != null) {
if (record.getNotification().getBubbleMetadata().isNotificationSuppressed()) {
- return true;
+ return MUTE_REASON_SUPPRESSED_BUBBLE;
}
}
- return false;
+ if (isPoliteNotificationFeatureEnabled(record)) {
+ // Notify the politeness strategy that an alerting notification is posted
+ if (!isInsistentUpdate(record)) {
+ mStrategy.onNotificationPosted(record);
+ }
+
+ // Suppress if politeness is muted and it's not an update for insistent
+ if (getPolitenessState(record) == PolitenessStrategy.POLITE_STATE_MUTED) {
+ return MUTE_REASON_COOLDOWN;
+ }
+ }
+
+ return MUTE_REASON_NOT_MUTED;
}
private boolean isLoopingRingtoneNotification(final NotificationRecord playingRecord) {
@@ -1201,12 +1249,6 @@ public final class NotificationAttentionHelper {
mApplyPerPackage = applyPerPackage;
}
- boolean shouldIgnoreNotification(final NotificationRecord record) {
- // Ignore auto-group summaries => don't count them as app-posted notifications
- // for the cooldown budget
- return (record.getSbn().isGroup() && GroupHelper.isAggregatedGroup(record));
- }
-
/**
* Get the key that determines the grouping for the cooldown behavior.
*
@@ -1358,10 +1400,6 @@ public final class NotificationAttentionHelper {
@Override
public void onNotificationPosted(final NotificationRecord record) {
- if (shouldIgnoreNotification(record)) {
- return;
- }
-
long timeSinceLastNotif =
System.currentTimeMillis() - getLastNotificationUpdateTimeMs(record);
@@ -1434,10 +1472,6 @@ public final class NotificationAttentionHelper {
@Override
void onNotificationPosted(NotificationRecord record) {
if (isAvalancheActive()) {
- if (shouldIgnoreNotification(record)) {
- return;
- }
-
long timeSinceLastNotif =
System.currentTimeMillis() - getLastNotificationUpdateTimeMs(record);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index c3a714b0eef0..655f2e4596aa 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -2579,6 +2579,7 @@ public class NotificationManagerService extends SystemService {
mNotificationChannelLogger,
mAppOps,
mUserProfiles,
+ mUgmInternal,
mShowReviewPermissionsNotification,
Clock.systemUTC());
mRankingHelper = new RankingHelper(getContext(), mRankingHandler, mPreferencesHelper,
@@ -6247,6 +6248,7 @@ public class NotificationManagerService extends SystemService {
int callingUid = Binder.getCallingUid();
@ZenModeConfig.ConfigOrigin int origin = computeZenOrigin(fromUser);
+ boolean isSystemCaller = isCallerSystemOrSystemUiOrShell();
boolean shouldApplyAsImplicitRule = android.app.Flags.modesApi()
&& !canManageGlobalZenPolicy(pkg, callingUid);
@@ -6283,11 +6285,33 @@ public class NotificationManagerService extends SystemService {
policy.priorityCallSenders, policy.priorityMessageSenders,
policy.suppressedVisualEffects, currPolicy.priorityConversationSenders);
}
+
int newVisualEffects = calculateSuppressedVisualEffects(
policy, currPolicy, applicationInfo.targetSdkVersion);
- policy = new Policy(policy.priorityCategories,
- policy.priorityCallSenders, policy.priorityMessageSenders,
- newVisualEffects, policy.priorityConversationSenders);
+
+ if (android.app.Flags.modesUi()) {
+ // 1. Callers should not modify STATE_CHANNELS_BYPASSING_DND, which is
+ // internally calculated and only indicates whether channels that want to bypass
+ // DND _exist_.
+ // 2. Only system callers should modify STATE_PRIORITY_CHANNELS_BLOCKED because
+ // it is @hide.
+ // 3. If the policy has been modified by the targetSdkVersion checks above then
+ // it has lost its state flags and that's fine (STATE_PRIORITY_CHANNELS_BLOCKED
+ // didn't exist until V).
+ int newState = Policy.STATE_UNSET;
+ if (isSystemCaller && policy.state != Policy.STATE_UNSET) {
+ newState = Policy.policyState(
+ currPolicy.hasPriorityChannels(),
+ policy.allowPriorityChannels());
+ }
+ policy = new Policy(policy.priorityCategories,
+ policy.priorityCallSenders, policy.priorityMessageSenders,
+ newVisualEffects, newState, policy.priorityConversationSenders);
+ } else {
+ policy = new Policy(policy.priorityCategories,
+ policy.priorityCallSenders, policy.priorityMessageSenders,
+ newVisualEffects, policy.priorityConversationSenders);
+ }
if (shouldApplyAsImplicitRule) {
mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, callingUid, policy);
@@ -6672,13 +6696,7 @@ public class NotificationManagerService extends SystemService {
final Uri originalSoundUri =
(originalChannel != null) ? originalChannel.getSound() : null;
if (soundUri != null && !Objects.equals(originalSoundUri, soundUri)) {
- Binder.withCleanCallingIdentity(() -> {
- mUgmInternal.checkGrantUriPermission(sourceUid, null,
- ContentProvider.getUriWithoutUserId(soundUri),
- Intent.FLAG_GRANT_READ_URI_PERMISSION,
- ContentProvider.getUserIdFromUri(soundUri,
- UserHandle.getUserId(sourceUid)));
- });
+ PermissionHelper.grantUriPermission(mUgmInternal, soundUri, sourceUid);
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index b9f0968b5864..3ba93845a290 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -1493,14 +1493,23 @@ public final class NotificationRecord {
final Notification notification = getNotification();
notification.visitUris((uri) -> {
- visitGrantableUri(uri, false, false);
+ if (com.android.server.notification.Flags.notificationVerifyChannelSoundUri()) {
+ visitGrantableUri(uri, false, false);
+ } else {
+ oldVisitGrantableUri(uri, false, false);
+ }
});
if (notification.getChannelId() != null) {
NotificationChannel channel = getChannel();
if (channel != null) {
- visitGrantableUri(channel.getSound(), (channel.getUserLockedFields()
- & NotificationChannel.USER_LOCKED_SOUND) != 0, true);
+ if (com.android.server.notification.Flags.notificationVerifyChannelSoundUri()) {
+ visitGrantableUri(channel.getSound(), (channel.getUserLockedFields()
+ & NotificationChannel.USER_LOCKED_SOUND) != 0, true);
+ } else {
+ oldVisitGrantableUri(channel.getSound(), (channel.getUserLockedFields()
+ & NotificationChannel.USER_LOCKED_SOUND) != 0, true);
+ }
}
}
} finally {
@@ -1516,7 +1525,7 @@ public final class NotificationRecord {
* {@link #mGrantableUris}. Otherwise, this will either log or throw
* {@link SecurityException} depending on target SDK of enqueuing app.
*/
- private void visitGrantableUri(Uri uri, boolean userOverriddenUri, boolean isSound) {
+ private void oldVisitGrantableUri(Uri uri, boolean userOverriddenUri, boolean isSound) {
if (uri == null || !ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) return;
if (mGrantableUris != null && mGrantableUris.contains(uri)) {
@@ -1555,6 +1564,45 @@ public final class NotificationRecord {
}
}
+ /**
+ * Note the presence of a {@link Uri} that should have permission granted to
+ * whoever will be rendering it.
+ * <p>
+ * If the enqueuing app has the ability to grant access, it will be added to
+ * {@link #mGrantableUris}. Otherwise, this will either log or throw
+ * {@link SecurityException} depending on target SDK of enqueuing app.
+ */
+ private void visitGrantableUri(Uri uri, boolean userOverriddenUri,
+ boolean isSound) {
+ if (mGrantableUris != null && mGrantableUris.contains(uri)) {
+ return; // already verified this URI
+ }
+
+ final int sourceUid = getSbn().getUid();
+ try {
+ PermissionHelper.grantUriPermission(mUgmInternal, uri, sourceUid);
+
+ if (mGrantableUris == null) {
+ mGrantableUris = new ArraySet<>();
+ }
+ mGrantableUris.add(uri);
+ } catch (SecurityException e) {
+ if (!userOverriddenUri) {
+ if (isSound) {
+ mSound = Settings.System.DEFAULT_NOTIFICATION_URI;
+ Log.w(TAG, "Replacing " + uri + " from " + sourceUid + ": " + e.getMessage());
+ } else {
+ if (mTargetSdkVersion >= Build.VERSION_CODES.P) {
+ throw e;
+ } else {
+ Log.w(TAG,
+ "Ignoring " + uri + " from " + sourceUid + ": " + e.getMessage());
+ }
+ }
+ }
+ }
+ }
+
public LogMaker getLogMaker(long now) {
LogMaker lm = getSbn().getLogMaker()
.addTaggedData(MetricsEvent.FIELD_NOTIFICATION_CHANNEL_IMPORTANCE, mImportance)
diff --git a/services/core/java/com/android/server/notification/PermissionHelper.java b/services/core/java/com/android/server/notification/PermissionHelper.java
index b6f48890c528..1464d481311a 100644
--- a/services/core/java/com/android/server/notification/PermissionHelper.java
+++ b/services/core/java/com/android/server/notification/PermissionHelper.java
@@ -25,19 +25,25 @@ import android.Manifest;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.companion.virtual.VirtualDeviceManager;
+import android.content.ContentProvider;
+import android.content.ContentResolver;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
+import android.net.Uri;
import android.os.Binder;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.permission.IPermissionManager;
import android.util.ArrayMap;
import android.util.Pair;
import android.util.Slog;
import com.android.internal.util.ArrayUtils;
+import com.android.server.uri.UriGrantsManagerInternal;
import java.util.Collections;
import java.util.HashSet;
@@ -58,7 +64,7 @@ public final class PermissionHelper {
private final IPermissionManager mPermManager;
public PermissionHelper(Context context, IPackageManager packageManager,
- IPermissionManager permManager) {
+ IPermissionManager permManager) {
mContext = context;
mPackageManager = packageManager;
mPermManager = permManager;
@@ -298,6 +304,19 @@ public final class PermissionHelper {
return false;
}
+ static void grantUriPermission(final UriGrantsManagerInternal ugmInternal, Uri uri,
+ int sourceUid) {
+ if (uri == null || !ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) return;
+
+ Binder.withCleanCallingIdentity(() -> {
+ // This will throw a SecurityException if the caller can't grant.
+ ugmInternal.checkGrantUriPermission(sourceUid, null,
+ ContentProvider.getUriWithoutUserId(uri),
+ Intent.FLAG_GRANT_READ_URI_PERMISSION,
+ ContentProvider.getUserIdFromUri(uri, UserHandle.getUserId(sourceUid)));
+ });
+ }
+
public static class PackagePermission {
public final String packageName;
public final @UserIdInt int userId;
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 85c395781d0a..9e70f815dff9 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -94,6 +94,7 @@ import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.notification.PermissionHelper.PackagePermission;
+import com.android.server.uri.UriGrantsManagerInternal;
import org.json.JSONArray;
import org.json.JSONException;
@@ -219,6 +220,7 @@ public class PreferencesHelper implements RankingConfig {
private final NotificationChannelLogger mNotificationChannelLogger;
private final AppOpsManager mAppOps;
private final ManagedServices.UserProfiles mUserProfiles;
+ private final UriGrantsManagerInternal mUgmInternal;
private SparseBooleanArray mBadgingEnabled;
private SparseBooleanArray mBubblesEnabled;
@@ -239,6 +241,7 @@ public class PreferencesHelper implements RankingConfig {
ZenModeHelper zenHelper, PermissionHelper permHelper, PermissionManager permManager,
NotificationChannelLogger notificationChannelLogger,
AppOpsManager appOpsManager, ManagedServices.UserProfiles userProfiles,
+ UriGrantsManagerInternal ugmInternal,
boolean showReviewPermissionsNotification, Clock clock) {
mContext = context;
mZenModeHelper = zenHelper;
@@ -249,6 +252,7 @@ public class PreferencesHelper implements RankingConfig {
mNotificationChannelLogger = notificationChannelLogger;
mAppOps = appOpsManager;
mUserProfiles = userProfiles;
+ mUgmInternal = ugmInternal;
mShowReviewPermissionsNotification = showReviewPermissionsNotification;
mIsMediaNotificationFilteringEnabled = context.getResources()
.getBoolean(R.bool.config_quickSettingsShowMediaPlayer);
@@ -1169,6 +1173,13 @@ public class PreferencesHelper implements RankingConfig {
}
clearLockedFieldsLocked(channel);
+ // Verify that the app has permission to read the sound Uri
+ // Only check for new channels, as regular apps can only set sound
+ // before creating. See: {@link NotificationChannel#setSound}
+ if (Flags.notificationVerifyChannelSoundUri()) {
+ PermissionHelper.grantUriPermission(mUgmInternal, channel.getSound(), uid);
+ }
+
channel.setImportanceLockedByCriticalDeviceFunction(
r.defaultAppLockedImportance || r.fixedImportance);
diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig
index be3adc142fa4..0b34177d7413 100644
--- a/services/core/java/com/android/server/notification/flags.aconfig
+++ b/services/core/java/com/android/server/notification/flags.aconfig
@@ -144,6 +144,13 @@ flag {
}
flag {
+ name: "notification_minimalism"
+ namespace: "systemui"
+ description: "Minimize the notifications to show on the lockscreen."
+ bug: "330387368"
+}
+
+flag {
name: "notification_force_group_singletons"
namespace: "systemui"
description: "This flag enables forced auto-grouping singleton groups"
@@ -163,3 +170,13 @@ flag {
description: "This flag enables sound uri with vibration source"
bug: "358524009"
}
+
+flag {
+ name: "notification_verify_channel_sound_uri"
+ namespace: "systemui"
+ description: "Verify Uri permission for sound when creating a notification channel"
+ bug: "337775777"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/services/core/java/com/android/server/pinner/PinRangeSource.java b/services/core/java/com/android/server/pinner/PinRangeSource.java
new file mode 100644
index 000000000000..5f9641122294
--- /dev/null
+++ b/services/core/java/com/android/server/pinner/PinRangeSource.java
@@ -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.server.pinner;
+
+/* package */ abstract class PinRangeSource {
+ /**
+ * Retrieve a range to pin.
+ *
+ * @param outPinRange Receives the pin region
+ * @return True if we filled in outPinRange or false if we're out of pin entries
+ */
+ abstract boolean read(PinnerService.PinRange outPinRange);
+} \ No newline at end of file
diff --git a/services/core/java/com/android/server/pinner/PinRangeSourceStatic.java b/services/core/java/com/android/server/pinner/PinRangeSourceStatic.java
new file mode 100644
index 000000000000..d6fc48790883
--- /dev/null
+++ b/services/core/java/com/android/server/pinner/PinRangeSourceStatic.java
@@ -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.server.pinner;
+
+/* package */ class PinRangeSourceStatic extends PinRangeSource {
+ private final int mPinStart;
+ private final int mPinLength;
+ private boolean mDone = false;
+
+ PinRangeSourceStatic(int pinStart, int pinLength) {
+ mPinStart = pinStart;
+ mPinLength = pinLength;
+ }
+
+ @Override
+ boolean read(PinnerService.PinRange outPinRange) {
+ outPinRange.start = mPinStart;
+ outPinRange.length = mPinLength;
+ boolean done = mDone;
+ mDone = true;
+ return !done;
+ }
+} \ No newline at end of file
diff --git a/services/core/java/com/android/server/pinner/PinRangeSourceStream.java b/services/core/java/com/android/server/pinner/PinRangeSourceStream.java
new file mode 100644
index 000000000000..79900b9de463
--- /dev/null
+++ b/services/core/java/com/android/server/pinner/PinRangeSourceStream.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pinner;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/* package */ final class PinRangeSourceStream extends PinRangeSource {
+ private final DataInputStream mStream;
+ private boolean mDone = false;
+
+ PinRangeSourceStream(InputStream stream) {
+ mStream = new DataInputStream(stream);
+ }
+
+ @Override
+ boolean read(PinnerService.PinRange outPinRange) {
+ if (!mDone) {
+ try {
+ outPinRange.start = mStream.readInt();
+ outPinRange.length = mStream.readInt();
+ } catch (IOException ex) {
+ mDone = true;
+ }
+ }
+ return !mDone;
+ }
+} \ No newline at end of file
diff --git a/services/core/java/com/android/server/pinner/PinnedFile.java b/services/core/java/com/android/server/pinner/PinnedFile.java
new file mode 100644
index 000000000000..a8de344d10af
--- /dev/null
+++ b/services/core/java/com/android/server/pinner/PinnedFile.java
@@ -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.server.pinner;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+
+@VisibleForTesting
+public final class PinnedFile implements AutoCloseable {
+ private long mAddress;
+ final long mapSize;
+ final String fileName;
+ public final long bytesPinned;
+
+ // Whether this file was pinned using a pinlist
+ boolean used_pinlist;
+
+ // User defined group name for pinner accounting
+ String groupName = "";
+ ArrayList<PinnedFile> pinnedDeps = new ArrayList<>();
+
+ public PinnedFile(long address, long mapSize, String fileName, long bytesPinned) {
+ mAddress = address;
+ this.mapSize = mapSize;
+ this.fileName = fileName;
+ this.bytesPinned = bytesPinned;
+ }
+
+ @Override
+ public void close() {
+ if (mAddress >= 0) {
+ PinnerUtils.safeMunmap(mAddress, mapSize);
+ mAddress = -1;
+ }
+ for (PinnedFile dep : pinnedDeps) {
+ if (dep != null) {
+ dep.close();
+ }
+ }
+ }
+
+ @Override
+ public void finalize() {
+ close();
+ }
+} \ No newline at end of file
diff --git a/services/core/java/com/android/server/PinnerService.java b/services/core/java/com/android/server/pinner/PinnerService.java
index ef03888d6620..d7ac5203ff53 100644
--- a/services/core/java/com/android/server/PinnerService.java
+++ b/services/core/java/com/android/server/pinner/PinnerService.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 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,14 +14,14 @@
* limitations under the License.
*/
-package com.android.server;
+package com.android.server.pinner;
import static android.app.ActivityManager.UID_OBSERVER_ACTIVE;
import static android.app.ActivityManager.UID_OBSERVER_GONE;
import static android.os.Process.SYSTEM_UID;
+import static com.android.server.flags.Flags.pinGlobalQuota;
import static com.android.server.flags.Flags.pinWebview;
-import static com.android.server.flags.Flags.skipHomeArtPins;
import android.annotation.EnforcePermission;
import android.annotation.IntDef;
@@ -49,6 +49,7 @@ import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.Looper;
import android.os.Message;
+import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
@@ -72,13 +73,13 @@ import com.android.internal.app.ResolverActivity;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.server.LocalServices;
+import com.android.server.SystemService;
import com.android.server.wm.ActivityTaskManagerInternal;
import dalvik.system.DexFile;
import dalvik.system.VMRuntime;
-import java.io.Closeable;
-import java.io.DataInputStream;
import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.IOException;
@@ -110,8 +111,7 @@ public final class PinnerService extends SystemService {
private static final String PIN_META_FILENAME = "pinlist.meta";
private static final int PAGE_SIZE = (int) Os.sysconf(OsConstants._SC_PAGESIZE);
private static final int MATCH_FLAGS = PackageManager.MATCH_DEFAULT_ONLY
- | PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
+ | PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
private static final int KEY_CAMERA = 0;
private static final int KEY_HOME = 1;
@@ -126,6 +126,8 @@ public final class PinnerService extends SystemService {
public static final String ANON_REGION_STAT_NAME = "[anon]";
+ private static final String SYSTEM_GROUP_NAME = "system";
+
@IntDef({KEY_CAMERA, KEY_HOME, KEY_ASSISTANT})
@Retention(RetentionPolicy.SOURCE)
public @interface AppKey {}
@@ -139,7 +141,8 @@ public final class PinnerService extends SystemService {
private final UserManager mUserManager;
/** The list of the statically pinned files. */
- @GuardedBy("this") private final ArrayMap<String, PinnedFile> mPinnedFiles = new ArrayMap<>();
+ @GuardedBy("this")
+ private final ArrayMap<String, PinnedFile> mPinnedFiles = new ArrayMap<>();
/** The list of the pinned apps. This is a map from {@link AppKey} to a pinned app. */
@GuardedBy("this")
@@ -159,8 +162,8 @@ public final class PinnerService extends SystemService {
/**
* A set of {@link AppKey} that are configured to be pinned.
*/
- @GuardedBy("this")
- private ArraySet<Integer> mPinKeys;
+ @GuardedBy("this") private
+ ArraySet<Integer> mPinKeys;
// Note that we don't use the `_BOOT` namespace for anonymous pinnings, as we want
// them to be responsive to dynamic flag changes for experimentation.
@@ -180,14 +183,23 @@ public final class PinnerService extends SystemService {
private final boolean mConfiguredToPinAssistant;
private final int mConfiguredWebviewPinBytes;
+ // This is the percentage of total device memory that will be used to set the global quota.
+ private final int mConfiguredMaxPinnedMemoryPercentage;
+
+ // This is the global pinner quota that can be pinned.
+ private long mConfiguredMaxPinnedMemory;
+
+ // This is the currently pinned memory.
+ private long mCurrentPinnedMemory = 0;
+
private BinderService mBinderService;
private PinnerHandler mPinnerHandler = null;
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- // If an app has updated, update pinned files accordingly.
- if (Intent.ACTION_PACKAGE_REPLACED.equals(intent.getAction())) {
+ // If an app has updated, update pinned files accordingly.
+ if (Intent.ACTION_PACKAGE_REPLACED.equals(intent.getAction())) {
Uri packageUri = intent.getData();
String packageName = packageUri.getSchemeSpecificPart();
ArraySet<String> updatedPackages = new ArraySet<>();
@@ -210,7 +222,7 @@ public final class PinnerService extends SystemService {
/** Utility class for testing. */
@VisibleForTesting
- static class Injector {
+ public static class Injector {
protected DeviceConfigInterface getDeviceConfigInterface() {
return DeviceConfigInterface.REAL;
}
@@ -219,9 +231,9 @@ public final class PinnerService extends SystemService {
service.publishBinderService("pinner", binderService);
}
- protected PinnedFile pinFileInternal(String fileToPin,
- int maxBytesToPin, boolean attemptPinIntrospection) {
- return PinnerService.pinFileInternal(fileToPin, maxBytesToPin, attemptPinIntrospection);
+ protected PinnedFile pinFileInternal(PinnerService service, String fileToPin,
+ long maxBytesToPin, boolean attemptPinIntrospection) {
+ return service.pinFileInternal(fileToPin, maxBytesToPin, attemptPinIntrospection);
}
}
@@ -230,7 +242,7 @@ public final class PinnerService extends SystemService {
}
@VisibleForTesting
- PinnerService(Context context, Injector injector) {
+ public PinnerService(Context context, Injector injector) {
super(context);
mContext = context;
@@ -244,6 +256,9 @@ public final class PinnerService extends SystemService {
com.android.internal.R.bool.config_pinnerAssistantApp);
mConfiguredWebviewPinBytes = context.getResources().getInteger(
com.android.internal.R.integer.config_pinnerWebviewPinBytes);
+ mConfiguredMaxPinnedMemoryPercentage = context.getResources().getInteger(
+ com.android.internal.R.integer.config_pinnerMaxPinnedMemoryPercentage);
+
mPinKeys = createPinKeys();
mPinnerHandler = new PinnerHandler(BackgroundThread.get().getLooper());
@@ -261,10 +276,8 @@ public final class PinnerService extends SystemService {
registerUidListener();
registerUserSetupCompleteListener();
- mDeviceConfigInterface.addOnPropertiesChangedListener(
- DEVICE_CONFIG_NAMESPACE_ANON_SIZE,
- new HandlerExecutor(mPinnerHandler),
- mDeviceConfigAnonSizeListener);
+ mDeviceConfigInterface.addOnPropertiesChangedListener(DEVICE_CONFIG_NAMESPACE_ANON_SIZE,
+ new HandlerExecutor(mPinnerHandler), mDeviceConfigAnonSizeListener);
}
@Override
@@ -272,6 +285,10 @@ public final class PinnerService extends SystemService {
if (DEBUG) {
Slog.i(TAG, "Starting PinnerService");
}
+ mConfiguredMaxPinnedMemory =
+ (Process.getTotalMemory()
+ * Math.clamp(mConfiguredMaxPinnedMemoryPercentage, 0, 100))
+ / 100;
mBinderService = new BinderService();
mInjector.publishBinderService(this, mBinderService);
publishLocalService(PinnerService.class, this);
@@ -348,7 +365,7 @@ public final class PinnerService extends SystemService {
protected PinnedFileStats(int uid, PinnedFile file) {
this.uid = uid;
this.filename = file.fileName.substring(file.fileName.lastIndexOf('/') + 1);
- this.sizeKb = file.bytesPinned / 1024;
+ this.sizeKb = (int) file.bytesPinned / 1024;
}
}
@@ -358,20 +375,11 @@ public final class PinnerService extends SystemService {
private void handlePinOnStart() {
// Files to pin come from the overlay and can be specified per-device config
String[] filesToPin = mContext.getResources().getStringArray(
- com.android.internal.R.array.config_defaultPinnerServiceFiles);
+ com.android.internal.R.array.config_defaultPinnerServiceFiles);
// Continue trying to pin each file even if we fail to pin some of them
for (String fileToPin : filesToPin) {
- PinnedFile pf = mInjector.pinFileInternal(fileToPin, Integer.MAX_VALUE,
- /*attemptPinIntrospection=*/false);
- if (pf == null) {
- Slog.e(TAG, "Failed to pin file = " + fileToPin);
- continue;
- }
- synchronized (this) {
- mPinnedFiles.put(pf.fileName, pf);
- }
- pf.groupName = "system";
- pinOptimizedDexDependencies(pf, Integer.MAX_VALUE, null);
+ pinFile(fileToPin, Integer.MAX_VALUE, /*appInfo=*/null, /*groupName=*/SYSTEM_GROUP_NAME,
+ true);
}
refreshPinAnonConfig();
@@ -383,10 +391,9 @@ public final class PinnerService extends SystemService {
* regular home app.
*/
private void registerUserSetupCompleteListener() {
- Uri userSetupCompleteUri = Settings.Secure.getUriFor(
- Settings.Secure.USER_SETUP_COMPLETE);
- mContext.getContentResolver().registerContentObserver(userSetupCompleteUri,
- false, new ContentObserver(null) {
+ Uri userSetupCompleteUri = Settings.Secure.getUriFor(Settings.Secure.USER_SETUP_COMPLETE);
+ mContext.getContentResolver().registerContentObserver(
+ userSetupCompleteUri, false, new ContentObserver(null) {
@Override
public void onChange(boolean selfChange, Uri uri) {
if (userSetupCompleteUri.equals(uri)) {
@@ -409,7 +416,7 @@ public final class PinnerService extends SystemService {
}
@Override
- public void onUidActive(int uid) {
+ public void onUidActive(int uid) {
mPinnerHandler.sendMessage(PooledLambda.obtainMessage(
PinnerService::handleUidActive, PinnerService.this, uid));
}
@@ -423,7 +430,6 @@ public final class PinnerService extends SystemService {
updateActiveState(uid, false /* active */);
int key;
synchronized (this) {
-
// In case we have a pending repin, repin now. See mPendingRepin for more information.
key = mPendingRepin.getOrDefault(uid, -1);
if (key == -1) {
@@ -491,8 +497,8 @@ public final class PinnerService extends SystemService {
private ApplicationInfo getCameraInfo(int userHandle) {
Intent cameraIntent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA);
- ApplicationInfo info = getApplicationInfoForIntent(cameraIntent, userHandle,
- false /* defaultToSystemApp */);
+ ApplicationInfo info = getApplicationInfoForIntent(
+ cameraIntent, userHandle, false /* defaultToSystemApp */);
// If the STILL_IMAGE_CAMERA intent doesn't resolve, try the _SECURE intent.
// We don't use _SECURE first because it will never get set on a device
@@ -501,16 +507,16 @@ public final class PinnerService extends SystemService {
// preference using this intent.
if (info == null) {
cameraIntent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE);
- info = getApplicationInfoForIntent(cameraIntent, userHandle,
- false /* defaultToSystemApp */);
+ info = getApplicationInfoForIntent(
+ cameraIntent, userHandle, false /* defaultToSystemApp */);
}
// If the _SECURE intent doesn't resolve, try the original intent but request
// the system app for camera if there was more than one result.
if (info == null) {
cameraIntent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA);
- info = getApplicationInfoForIntent(cameraIntent, userHandle,
- true /* defaultToSystemApp */);
+ info = getApplicationInfoForIntent(
+ cameraIntent, userHandle, true /* defaultToSystemApp */);
}
return info;
}
@@ -525,14 +531,14 @@ public final class PinnerService extends SystemService {
return getApplicationInfoForIntent(intent, userHandle, true);
}
- private ApplicationInfo getApplicationInfoForIntent(Intent intent, int userHandle,
- boolean defaultToSystemApp) {
+ private ApplicationInfo getApplicationInfoForIntent(
+ Intent intent, int userHandle, boolean defaultToSystemApp) {
if (intent == null) {
return null;
}
- ResolveInfo resolveInfo = mContext.getPackageManager().resolveActivityAsUser(intent,
- MATCH_FLAGS, userHandle);
+ ResolveInfo resolveInfo =
+ mContext.getPackageManager().resolveActivityAsUser(intent, MATCH_FLAGS, userHandle);
// If this intent can resolve to only one app, choose that one.
// Otherwise, if we've requested to default to the system app, return it;
@@ -547,12 +553,11 @@ public final class PinnerService extends SystemService {
}
if (defaultToSystemApp) {
- List<ResolveInfo> infoList = mContext.getPackageManager()
- .queryIntentActivitiesAsUser(intent, MATCH_FLAGS, userHandle);
+ List<ResolveInfo> infoList = mContext.getPackageManager().queryIntentActivitiesAsUser(
+ intent, MATCH_FLAGS, userHandle);
ApplicationInfo systemAppInfo = null;
for (ResolveInfo info : infoList) {
- if ((info.activityInfo.applicationInfo.flags
- & ApplicationInfo.FLAG_SYSTEM) != 0) {
+ if ((info.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
if (systemAppInfo == null) {
systemAppInfo = info.activityInfo.applicationInfo;
} else {
@@ -568,13 +573,13 @@ public final class PinnerService extends SystemService {
}
private void sendPinAppsMessage(int userHandle) {
- mPinnerHandler.sendMessage(PooledLambda.obtainMessage(PinnerService::pinApps, this,
- userHandle));
+ mPinnerHandler.sendMessage(
+ PooledLambda.obtainMessage(PinnerService::pinApps, this, userHandle));
}
private void sendPinAppsWithUpdatedKeysMessage(int userHandle) {
- mPinnerHandler.sendMessage(PooledLambda.obtainMessage(PinnerService::pinAppsWithUpdatedKeys,
- this, userHandle));
+ mPinnerHandler.sendMessage(PooledLambda.obtainMessage(
+ PinnerService::pinAppsWithUpdatedKeys, this, userHandle));
}
private void sendUnpinAppsMessage() {
mPinnerHandler.sendMessage(PooledLambda.obtainMessage(PinnerService::unpinApps, this));
@@ -586,8 +591,7 @@ public final class PinnerService extends SystemService {
// phenotype property is not set.
boolean shouldPinCamera = mConfiguredToPinCamera
&& mDeviceConfigInterface.getBoolean(DeviceConfig.NAMESPACE_RUNTIME_NATIVE_BOOT,
- "pin_camera",
- SystemProperties.getBoolean("pinner.pin_camera", true));
+ "pin_camera", SystemProperties.getBoolean("pinner.pin_camera", true));
if (shouldPinCamera) {
pinKeys.add(KEY_CAMERA);
} else if (DEBUG) {
@@ -626,8 +630,9 @@ public final class PinnerService extends SystemService {
synchronized (this) {
// This code path demands preceding unpinApps() call.
if (!mPinnedApps.isEmpty()) {
- Slog.e(TAG, "Attempted to update a list of apps, "
- + "but apps were already pinned. Skipping.");
+ Slog.e(TAG,
+ "Attempted to update a list of apps, "
+ + "but apps were already pinned. Skipping.");
return;
}
@@ -646,8 +651,8 @@ public final class PinnerService extends SystemService {
* @see #pinApp(int, int, boolean)
*/
private void sendPinAppMessage(int key, int userHandle, boolean force) {
- mPinnerHandler.sendMessage(PooledLambda.obtainMessage(PinnerService::pinApp, this,
- key, userHandle, force));
+ mPinnerHandler.sendMessage(
+ PooledLambda.obtainMessage(PinnerService::pinApp, this, key, userHandle, force));
}
/**
@@ -667,10 +672,10 @@ public final class PinnerService extends SystemService {
}
return;
}
- unpinApp(key);
ApplicationInfo info = getInfoForKey(key, userHandle);
+ unpinApp(key);
if (info != null) {
- pinApp(key, info);
+ pinAppInternal(key, info);
}
}
@@ -682,9 +687,7 @@ public final class PinnerService extends SystemService {
private int getUidForKey(@AppKey int key) {
synchronized (this) {
PinnedApp existing = mPinnedApps.get(key);
- return existing != null && existing.active
- ? existing.uid
- : -1;
+ return existing != null && existing.active ? existing.uid : -1;
}
}
@@ -727,11 +730,8 @@ public final class PinnerService extends SystemService {
* Handle any changes in the anon region pinner config.
*/
private void refreshPinAnonConfig() {
- long newPinAnonSize =
- mDeviceConfigInterface.getLong(
- DEVICE_CONFIG_NAMESPACE_ANON_SIZE,
- DEVICE_CONFIG_KEY_ANON_SIZE,
- DEFAULT_ANON_SIZE);
+ long newPinAnonSize = mDeviceConfigInterface.getLong(
+ DEVICE_CONFIG_NAMESPACE_ANON_SIZE, DEVICE_CONFIG_KEY_ANON_SIZE, DEFAULT_ANON_SIZE);
newPinAnonSize = Math.max(0, Math.min(newPinAnonSize, MAX_ANON_SIZE));
if (newPinAnonSize != mPinAnonSize) {
mPinAnonSize = newPinAnonSize;
@@ -765,10 +765,9 @@ public final class PinnerService extends SystemService {
try {
// Map as SHARED to avoid changing rss.anon for system_server (per /proc/*/status).
// The mapping is visible in other rss metrics, and as private dirty in smaps/meminfo.
- address = Os.mmap(0, alignedPinSize,
- OsConstants.PROT_READ | OsConstants.PROT_WRITE,
- OsConstants.MAP_SHARED | OsConstants.MAP_ANONYMOUS,
- new FileDescriptor(), /*offset=*/0);
+ address = Os.mmap(0, alignedPinSize, OsConstants.PROT_READ | OsConstants.PROT_WRITE,
+ OsConstants.MAP_SHARED | OsConstants.MAP_ANONYMOUS, new FileDescriptor(),
+ /*offset=*/0);
Unsafe tempUnsafe = null;
Class<sun.misc.Unsafe> clazz = sun.misc.Unsafe.class;
@@ -794,14 +793,14 @@ public final class PinnerService extends SystemService {
return;
} finally {
if (address >= 0) {
- safeMunmap(address, alignedPinSize);
+ PinnerUtils.safeMunmap(address, alignedPinSize);
}
}
}
private void unpinAnonRegion() {
if (mPinAnonAddress != 0) {
- safeMunmap(mPinAnonAddress, mCurrentlyPinnedAnonSize);
+ PinnerUtils.safeMunmap(mPinAnonAddress, mCurrentlyPinnedAnonSize);
}
mPinAnonAddress = 0;
mCurrentlyPinnedAnonSize = 0;
@@ -824,12 +823,20 @@ public final class PinnerService extends SystemService {
}
/**
+ * Retrieves remaining quota for pinner service, once it reaches 0 it will no longer
+ * pin any file.
+ */
+ private long getAvailableGlobalQuota() {
+ return mConfiguredMaxPinnedMemory - mCurrentPinnedMemory;
+ }
+
+ /**
* Pins an application.
*
* @param key The key of the app to pin.
* @param appInfo The corresponding app info.
*/
- private void pinApp(@AppKey int key, @Nullable ApplicationInfo appInfo) {
+ private void pinAppInternal(@AppKey int key, @Nullable ApplicationInfo appInfo) {
if (appInfo == null) {
return;
}
@@ -839,7 +846,6 @@ public final class PinnerService extends SystemService {
mPinnedApps.put(key, pinnedApp);
}
-
// pin APK
final int pinSizeLimit = getSizeLimitForKey(key);
List<String> apks = new ArrayList<>();
@@ -851,36 +857,31 @@ public final class PinnerService extends SystemService {
}
}
- int apkPinSizeLimit = pinSizeLimit;
-
- boolean shouldSkipArtPins = key == KEY_HOME && skipHomeArtPins();
+ long apkPinSizeLimit = pinSizeLimit;
- for (String apk: apks) {
+ for (String apk : apks) {
if (apkPinSizeLimit <= 0) {
Slog.w(TAG, "Reached to the pin size limit. Skipping: " + apk);
// Continue instead of break to print all skipped APK names.
continue;
}
- PinnedFile pf = mInjector.pinFileInternal(apk, apkPinSizeLimit, /*attemptPinIntrospection=*/true);
+ String pinGroup = getNameForKey(key);
+ boolean shouldPinDeps = apk.equals(appInfo.sourceDir);
+ PinnedFile pf = pinFile(apk, apkPinSizeLimit, appInfo, pinGroup, shouldPinDeps);
if (pf == null) {
Slog.e(TAG, "Failed to pin " + apk);
continue;
}
- pf.groupName = getNameForKey(key);
if (DEBUG) {
Slog.i(TAG, "Pinned " + pf.fileName);
}
synchronized (this) {
pinnedApp.mFiles.add(pf);
- mPinnedFiles.put(pf.fileName, pf);
}
apkPinSizeLimit -= pf.bytesPinned;
- if (apk.equals(appInfo.sourceDir) && !shouldSkipArtPins) {
- pinOptimizedDexDependencies(pf, Integer.MAX_VALUE, appInfo);
- }
}
}
@@ -892,19 +893,23 @@ public final class PinnerService extends SystemService {
* that related to the file but not within itself.
*
* @param fileToPin File to pin
- * @param maxBytesToPin maximum quota allowed for pinning
- * @return total bytes that were pinned.
+ * @param bytesRequestedToPin maximum bytes requested to pin for {@code fileToPin}.
+ * @param pinOptimizedDeps whether optimized dependencies such as odex,vdex, etc be pinned.
+ * Note: {@code bytesRequestedToPin} limit will not apply to optimized
+ * dependencies pinned, only global quotas will apply instead.
+ * @return pinned file
*/
- public int pinFile(String fileToPin, int maxBytesToPin, @Nullable ApplicationInfo appInfo,
- @Nullable String groupName) {
+ public PinnedFile pinFile(String fileToPin, long bytesRequestedToPin,
+ @Nullable ApplicationInfo appInfo, @Nullable String groupName,
+ boolean pinOptimizedDeps) {
PinnedFile existingPin;
- synchronized(this) {
+ synchronized (this) {
existingPin = mPinnedFiles.get(fileToPin);
}
if (existingPin != null) {
- if (existingPin.bytesPinned == maxBytesToPin) {
+ if (existingPin.bytesPinned == bytesRequestedToPin) {
// Duplicate pin requesting same amount of bytes, lets just bail out.
- return 0;
+ return null;
} else {
// User decided to pin a different amount of bytes than currently pinned
// so this is a valid pin request. Unpin the previous version before repining.
@@ -915,26 +920,38 @@ public final class PinnerService extends SystemService {
}
}
+ long remainingQuota = getAvailableGlobalQuota();
+
+ if (pinGlobalQuota()) {
+ if (remainingQuota <= 0) {
+ Slog.w(TAG, "Reached pin quota, skipping file: " + fileToPin);
+ return null;
+ }
+ bytesRequestedToPin = Math.min(bytesRequestedToPin, remainingQuota);
+ }
+
boolean isApk = fileToPin.endsWith(".apk");
- int bytesPinned = 0;
- PinnedFile pf = mInjector.pinFileInternal(fileToPin, maxBytesToPin,
+
+ PinnedFile pf = mInjector.pinFileInternal(this, fileToPin, bytesRequestedToPin,
/*attemptPinIntrospection=*/isApk);
if (pf == null) {
Slog.e(TAG, "Failed to pin file = " + fileToPin);
- return 0;
+ return null;
}
pf.groupName = groupName != null ? groupName : "";
- bytesPinned += pf.bytesPinned;
- maxBytesToPin -= bytesPinned;
+ mCurrentPinnedMemory += pf.bytesPinned;
synchronized (this) {
mPinnedFiles.put(pf.fileName, pf);
}
- if (maxBytesToPin > 0) {
- pinOptimizedDexDependencies(pf, maxBytesToPin, appInfo);
+
+ if (pinOptimizedDeps) {
+ mCurrentPinnedMemory +=
+ pinOptimizedDexDependencies(pf, getAvailableGlobalQuota(), appInfo);
}
- return bytesPinned;
+
+ return pf;
}
/**
@@ -945,13 +962,13 @@ public final class PinnerService extends SystemService {
* to null it will use the default supported ABI by the device.
* @return total bytes pinned.
*/
- private int pinOptimizedDexDependencies(
- PinnedFile pinnedFile, int maxBytesToPin, @Nullable ApplicationInfo appInfo) {
+ private long pinOptimizedDexDependencies(
+ PinnedFile pinnedFile, long maxBytesToPin, @Nullable ApplicationInfo appInfo) {
if (pinnedFile == null) {
return 0;
}
- int bytesPinned = 0;
+ long bytesPinned = 0;
if (pinnedFile.fileName.endsWith(".jar") | pinnedFile.fileName.endsWith(".apk")) {
String abi = null;
if (appInfo != null) {
@@ -974,7 +991,7 @@ public final class PinnerService extends SystemService {
// Unpin if it was already pinned prior to re-pinning.
unpinFile(file);
- PinnedFile df = mInjector.pinFileInternal(file, maxBytesToPin,
+ PinnedFile df = mInjector.pinFileInternal(this, file, maxBytesToPin,
/*attemptPinIntrospection=*/false);
if (df == null) {
Slog.i(TAG, "Failed to pin ART file = " + file);
@@ -992,7 +1009,8 @@ public final class PinnerService extends SystemService {
return bytesPinned;
}
- /** mlock length bytes of fileToPin in memory
+ /**
+ * mlock length bytes of fileToPin in memory
*
* If attemptPinIntrospection is true, then treat the file to pin as a zip file and
* look for a "pinlist.meta" file in the archive root directory. The structure of this
@@ -1029,8 +1047,8 @@ public final class PinnerService extends SystemService {
* zip in order to extract the
* @return Pinned memory resource owner thing or null on error
*/
- private static PinnedFile pinFileInternal(
- String fileToPin, int maxBytesToPin, boolean attemptPinIntrospection) {
+ private PinnedFile pinFileInternal(
+ String fileToPin, long maxBytesToPin, boolean attemptPinIntrospection) {
if (DEBUG) {
Slog.d(TAG, "pin file: " + fileToPin + " use-pinlist: " + attemptPinIntrospection);
}
@@ -1054,8 +1072,8 @@ public final class PinnerService extends SystemService {
}
return pinnedFile;
} finally {
- safeClose(pinRangeStream);
- safeClose(fileAsZip); // Also closes any streams we've opened
+ PinnerUtils.safeClose(pinRangeStream);
+ PinnerUtils.safeClose(fileAsZip); // Also closes any streams we've opened
}
}
@@ -1068,11 +1086,8 @@ public final class PinnerService extends SystemService {
try {
zip = new ZipFile(fileName);
} catch (IOException ex) {
- Slog.w(TAG,
- String.format(
- "could not open \"%s\" as zip: pinning as blob",
- fileName),
- ex);
+ Slog.w(TAG, String.format("could not open \"%s\" as zip: pinning as blob", fileName),
+ ex);
}
return zip;
}
@@ -1112,9 +1127,9 @@ public final class PinnerService extends SystemService {
pinMetaStream = zipFile.getInputStream(pinMetaEntry);
} catch (IOException ex) {
Slog.w(TAG,
- String.format("error reading pin metadata \"%s\": pinning as blob",
- fileName),
- ex);
+ String.format(
+ "error reading pin metadata \"%s\": pinning as blob", fileName),
+ ex);
}
} else {
Slog.w(TAG,
@@ -1124,57 +1139,6 @@ public final class PinnerService extends SystemService {
return pinMetaStream;
}
- private static abstract class PinRangeSource {
- /** Retrive a range to pin.
- *
- * @param outPinRange Receives the pin region
- * @return True if we filled in outPinRange or false if we're out of pin entries
- */
- abstract boolean read(PinRange outPinRange);
- }
-
- private static final class PinRangeSourceStatic extends PinRangeSource {
- private final int mPinStart;
- private final int mPinLength;
- private boolean mDone = false;
-
- PinRangeSourceStatic(int pinStart, int pinLength) {
- mPinStart = pinStart;
- mPinLength = pinLength;
- }
-
- @Override
- boolean read(PinRange outPinRange) {
- outPinRange.start = mPinStart;
- outPinRange.length = mPinLength;
- boolean done = mDone;
- mDone = true;
- return !done;
- }
- }
-
- private static final class PinRangeSourceStream extends PinRangeSource {
- private final DataInputStream mStream;
- private boolean mDone = false;
-
- PinRangeSourceStream(InputStream stream) {
- mStream = new DataInputStream(stream);
- }
-
- @Override
- boolean read(PinRange outPinRange) {
- if (!mDone) {
- try {
- outPinRange.start = mStream.readInt();
- outPinRange.length = mStream.readInt();
- } catch (IOException ex) {
- mDone = true;
- }
- }
- return !mDone;
- }
- }
-
/**
* Helper for pinFile.
*
@@ -1185,25 +1149,20 @@ public final class PinnerService extends SystemService {
* @return PinnedFile or null on error
*/
private static PinnedFile pinFileRanges(
- String fileToPin,
- int maxBytesToPin,
- PinRangeSource pinRangeSource)
- {
+ String fileToPin, long maxBytesToPin, PinRangeSource pinRangeSource) {
FileDescriptor fd = new FileDescriptor();
long address = -1;
- int mapSize = 0;
+ long mapSize = 0;
try {
int openFlags = (OsConstants.O_RDONLY | OsConstants.O_CLOEXEC);
fd = Os.open(fileToPin, openFlags, 0);
mapSize = (int) Math.min(Os.fstat(fd).st_size, Integer.MAX_VALUE);
- address = Os.mmap(0, mapSize,
- OsConstants.PROT_READ,
- OsConstants.MAP_SHARED,
- fd, /*offset=*/0);
+ address = Os.mmap(
+ 0, mapSize, OsConstants.PROT_READ, OsConstants.MAP_SHARED, fd, /*offset=*/0);
PinRange pinRange = new PinRange();
- int bytesPinned = 0;
+ long bytesPinned = 0;
// We pin at page granularity, so make sure the limit is page-aligned
if (maxBytesToPin % PAGE_SIZE != 0) {
@@ -1211,10 +1170,10 @@ public final class PinnerService extends SystemService {
}
while (bytesPinned < maxBytesToPin && pinRangeSource.read(pinRange)) {
- int pinStart = pinRange.start;
- int pinLength = pinRange.length;
- pinStart = clamp(0, pinStart, mapSize);
- pinLength = clamp(0, pinLength, mapSize - pinStart);
+ long pinStart = pinRange.start;
+ long pinLength = pinRange.length;
+ pinStart = PinnerUtils.clamp(0, pinStart, mapSize);
+ pinLength = PinnerUtils.clamp(0, pinLength, mapSize - pinStart);
pinLength = Math.min(maxBytesToPin - bytesPinned, pinLength);
// mlock doesn't require the region to be page-aligned, but we snap the
@@ -1229,14 +1188,13 @@ public final class PinnerService extends SystemService {
if (pinLength % PAGE_SIZE != 0) {
pinLength += PAGE_SIZE - pinLength % PAGE_SIZE;
}
- pinLength = clamp(0, pinLength, maxBytesToPin - bytesPinned);
+ pinLength = PinnerUtils.clamp(0, pinLength, maxBytesToPin - bytesPinned);
if (pinLength > 0) {
if (DEBUG) {
Slog.d(TAG,
- String.format(
- "pinning at %s %s bytes of %s",
- pinStart, pinLength, fileToPin));
+ String.format("pinning at %s %s bytes of %s", pinStart, pinLength,
+ fileToPin));
}
Os.mlock(address + pinStart, pinLength);
}
@@ -1244,15 +1202,15 @@ public final class PinnerService extends SystemService {
}
PinnedFile pinnedFile = new PinnedFile(address, mapSize, fileToPin, bytesPinned);
- address = -1; // Ownership transferred
+ address = -1; // Ownership transferred
return pinnedFile;
} catch (ErrnoException ex) {
Slog.e(TAG, "Could not pin file " + fileToPin, ex);
return null;
} finally {
- safeClose(fd);
+ PinnerUtils.safeClose(fd);
if (address >= 0) {
- safeMunmap(address, mapSize);
+ PinnerUtils.safeMunmap(address, mapSize);
}
}
}
@@ -1273,81 +1231,50 @@ public final class PinnerService extends SystemService {
}
}
- public void unpinFile(String filename) {
+ /**
+ * Unpin a file and its optimized dependencies.
+ *
+ * @param filename file to unpin.
+ * @return number of bytes unpinned, 0 in case of failure or nothing to unpin.
+ */
+ public long unpinFile(String filename) {
PinnedFile pinnedFile;
synchronized (this) {
pinnedFile = mPinnedFiles.get(filename);
}
if (pinnedFile == null) {
// File not pinned, nothing to do.
- return;
+ return 0;
}
+ long unpinnedBytes = pinnedFile.bytesPinned;
pinnedFile.close();
synchronized (this) {
if (DEBUG) {
Slog.d(TAG, "Unpinned file: " + filename);
}
+ mCurrentPinnedMemory -= pinnedFile.bytesPinned;
+
mPinnedFiles.remove(pinnedFile.fileName);
for (PinnedFile dep : pinnedFile.pinnedDeps) {
if (dep == null) {
continue;
}
+ unpinnedBytes -= dep.bytesPinned;
+ mCurrentPinnedMemory -= dep.bytesPinned;
mPinnedFiles.remove(dep.fileName);
if (DEBUG) {
Slog.d(TAG, "Unpinned dependency: " + dep.fileName);
}
}
}
- }
- private static int clamp(int min, int value, int max) {
- return Math.max(min, Math.min(value, max));
- }
-
- private static void safeMunmap(long address, long mapSize) {
- try {
- Os.munmap(address, mapSize);
- } catch (ErrnoException ex) {
- Slog.w(TAG, "ignoring error in unmap", ex);
- }
- }
-
- /**
- * Close FD, swallowing irrelevant errors.
- */
- private static void safeClose(@Nullable FileDescriptor fd) {
- if (fd != null && fd.valid()) {
- try {
- Os.close(fd);
- } catch (ErrnoException ex) {
- // Swallow the exception: non-EBADF errors in close(2)
- // indicate deferred paging write errors, which we
- // don't care about here. The underlying file
- // descriptor is always closed.
- if (ex.errno == OsConstants.EBADF) {
- throw new AssertionError(ex);
- }
- }
- }
- }
-
- /**
- * Close closeable thing, swallowing errors.
- */
- private static void safeClose(@Nullable Closeable thing) {
- if (thing != null) {
- try {
- thing.close();
- } catch (IOException ex) {
- Slog.w(TAG, "ignoring error closing resource: " + thing, ex);
- }
- }
+ return unpinnedBytes;
}
public List<PinnedFileStat> getPinnerStats() {
ArrayList<PinnedFileStat> stats = new ArrayList<>();
Collection<PinnedFile> pinnedFiles;
- synchronized(this) {
+ synchronized (this) {
pinnedFiles = mPinnedFiles.values();
}
for (PinnedFile pf : pinnedFiles) {
@@ -1355,8 +1282,8 @@ public final class PinnerService extends SystemService {
stats.add(stat);
}
if (mCurrentlyPinnedAnonSize > 0) {
- stats.add(new PinnedFileStat(ANON_REGION_STAT_NAME,
- mCurrentlyPinnedAnonSize, ANON_REGION_STAT_NAME));
+ stats.add(new PinnedFileStat(
+ ANON_REGION_STAT_NAME, mCurrentlyPinnedAnonSize, ANON_REGION_STAT_NAME));
}
return stats;
}
@@ -1364,71 +1291,124 @@ public final class PinnerService extends SystemService {
public final class BinderService extends IPinnerService.Stub {
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
+ if (!DumpUtils.checkDumpPermission(mContext, TAG, pw))
+ return;
HashSet<PinnedFile> shownPins = new HashSet<>();
- HashSet<String> groups = new HashSet<>();
- final int bytesPerMB = 1024 * 1024;
+ HashSet<String> shownGroups = new HashSet<>();
+ HashSet<String> groupsToPrint = new HashSet<>();
+ final double bytesPerMB = 1024 * 1024;
+ pw.format("Pinner Configs:\n");
+ pw.format(" Total Pinner quota: %d%% of total device memory\n",
+ mConfiguredMaxPinnedMemoryPercentage);
+ pw.format(" Maximum Pinner quota: %d bytes (%.2f MB)\n", mConfiguredMaxPinnedMemory,
+ mConfiguredMaxPinnedMemory / bytesPerMB);
+ pw.format(" Max Home App Pin Bytes (without deps): %d\n", mConfiguredHomePinBytes);
+ pw.format("\nPinned Files:\n");
synchronized (PinnerService.this) {
long totalSize = 0;
+
+ // We print apps separately from regular pins as they contain extra information that
+ // other pins do not.
for (int key : mPinnedApps.keySet()) {
PinnedApp app = mPinnedApps.get(key);
pw.print(getNameForKey(key));
- pw.print(" uid="); pw.print(app.uid);
- pw.print(" active="); pw.print(app.active);
+ pw.print(" uid=");
+ pw.print(app.uid);
+ pw.print(" active=");
+ pw.print(app.active);
+
+ if (!app.mFiles.isEmpty()) {
+ shownGroups.add(app.mFiles.getFirst().groupName);
+ }
pw.println();
+ long bytesPinnedForApp = 0;
+ long bytesPinnedForAppDeps = 0;
for (PinnedFile pf : mPinnedApps.get(key).mFiles) {
pw.print(" ");
- pw.format("%s pinned:%d bytes (%d MB) pinlist:%b\n", pf.fileName,
+ pw.format("%s pinned:%d bytes (%.2f MB) pinlist:%b\n", pf.fileName,
pf.bytesPinned, pf.bytesPinned / bytesPerMB, pf.used_pinlist);
totalSize += pf.bytesPinned;
+ bytesPinnedForApp += pf.bytesPinned;
shownPins.add(pf);
for (PinnedFile dep : pf.pinnedDeps) {
pw.print(" ");
- pw.format("%s pinned:%d bytes (%d MB) pinlist:%b (Dependency)\n", dep.fileName,
- dep.bytesPinned, dep.bytesPinned / bytesPerMB, dep.used_pinlist);
+ pw.format("%s pinned:%d bytes (%.2f MB) pinlist:%b (Dependency)\n",
+ dep.fileName, dep.bytesPinned, dep.bytesPinned / bytesPerMB,
+ dep.used_pinlist);
totalSize += dep.bytesPinned;
+ bytesPinnedForAppDeps += dep.bytesPinned;
shownPins.add(dep);
}
}
+ long bytesPinnedForAppAndDeps = bytesPinnedForApp + bytesPinnedForAppDeps;
+ pw.format("Total Pinned = %d (%.2f MB) [App=%d (%.2f MB), "
+ + "Dependencies=%d (%.2f MB)]\n\n",
+ bytesPinnedForAppAndDeps, bytesPinnedForAppAndDeps / bytesPerMB,
+ bytesPinnedForApp, bytesPinnedForApp / bytesPerMB,
+ bytesPinnedForAppDeps, bytesPinnedForAppDeps / bytesPerMB);
}
pw.println();
for (PinnedFile pinnedFile : mPinnedFiles.values()) {
- if (!groups.contains(pinnedFile.groupName)) {
- groups.add(pinnedFile.groupName);
+ if (!groupsToPrint.contains(pinnedFile.groupName)
+ && !shownGroups.contains(pinnedFile.groupName)) {
+ groupsToPrint.add(pinnedFile.groupName);
}
}
- boolean firstPinInGroup = true;
- for (String group : groups) {
+
+ // Print all the non app groups.
+ for (String group : groupsToPrint) {
List<PinnedFile> groupPins = getAllPinsForGroup(group);
+ pw.print("\nGroup:" + group);
+ long bytesPinnedForGroupNoDeps = 0;
+ long bytesPinnedForGroupDeps = 0;
+ pw.println();
for (PinnedFile pinnedFile : groupPins) {
if (shownPins.contains(pinnedFile)) {
- // Already showed in the dump and accounted for, skip.
+ // Already displayed and accounted for, skip.
continue;
}
- if (firstPinInGroup) {
- firstPinInGroup = false;
- // Ensure we only print when there are pins for groups not yet shown
- // in the pinned app section.
- pw.print("Group:" + group);
- pw.println();
- }
- pw.format(" %s pinned:%d bytes (%d MB) pinlist:%b\n", pinnedFile.fileName,
- pinnedFile.bytesPinned, pinnedFile.bytesPinned / bytesPerMB,
- pinnedFile.used_pinlist);
+ pw.format(" %s pinned: %d bytes (%.2f MB) pinlist:%b\n",
+ pinnedFile.fileName, pinnedFile.bytesPinned,
+ pinnedFile.bytesPinned / bytesPerMB, pinnedFile.used_pinlist);
totalSize += pinnedFile.bytesPinned;
+ bytesPinnedForGroupNoDeps += pinnedFile.bytesPinned;
+ shownPins.add(pinnedFile);
+ for (PinnedFile dep : pinnedFile.pinnedDeps) {
+ if (shownPins.contains(dep)) {
+ // Already displayed and accounted for, skip.
+ continue;
+ }
+ pw.print(" ");
+ pw.format("%s pinned:%d bytes (%.2f MB) pinlist:%b (Dependency)\n",
+ dep.fileName, dep.bytesPinned, dep.bytesPinned / bytesPerMB,
+ dep.used_pinlist);
+ totalSize += dep.bytesPinned;
+ bytesPinnedForGroupDeps += dep.bytesPinned;
+ shownPins.add(dep);
+ }
}
+ long bytesPinnedForGroup = bytesPinnedForGroupNoDeps + bytesPinnedForGroupDeps;
+ pw.format("Total Pinned = %d (%.2f MB) [Main=%d (%.2f MB), "
+ + "Dependencies=%d (%.2f MB)]\n\n",
+ bytesPinnedForGroup, bytesPinnedForGroup / bytesPerMB,
+ bytesPinnedForGroupNoDeps, bytesPinnedForGroupNoDeps / bytesPerMB,
+ bytesPinnedForGroupDeps, bytesPinnedForGroupDeps / bytesPerMB);
}
pw.println();
if (mPinAnonAddress != 0) {
- pw.format("Pinned anon region: %d (%d MB)\n", mCurrentlyPinnedAnonSize, mCurrentlyPinnedAnonSize / bytesPerMB);
+ pw.format("Pinned anon region: %d (%.2f MB)\n", mCurrentlyPinnedAnonSize,
+ mCurrentlyPinnedAnonSize / bytesPerMB);
totalSize += mCurrentlyPinnedAnonSize;
}
- pw.format("Total pinned: %s bytes (%s MB)\n", totalSize, totalSize / bytesPerMB);
+ pw.format("Total pinned: %d bytes (%.2f MB)\n", totalSize, totalSize / bytesPerMB);
+ pw.format("Available Pinner quota: %d bytes (%.2f MB)\n", getAvailableGlobalQuota(),
+ getAvailableGlobalQuota() / bytesPerMB);
pw.println();
if (!mPendingRepin.isEmpty()) {
pw.print("Pending repin: ");
for (int key : mPendingRepin.values()) {
- pw.print(getNameForKey(key)); pw.print(' ');
+ pw.print(getNameForKey(key));
+ pw.print(' ');
}
pw.println();
}
@@ -1462,8 +1442,9 @@ public final class PinnerService extends SystemService {
repin();
break;
default:
- printError(out, String.format(
- "Unknown pinner command: %s. Supported commands: repin", command));
+ printError(out,
+ String.format("Unknown pinner command: %s. Supported commands: repin",
+ command));
resultReceiver.send(-1, null);
return;
}
@@ -1479,46 +1460,6 @@ public final class PinnerService extends SystemService {
}
}
- @VisibleForTesting
- public static final class PinnedFile implements AutoCloseable {
- private long mAddress;
- final int mapSize;
- final String fileName;
- final int bytesPinned;
-
- // Whether this file was pinned using a pinlist
- boolean used_pinlist;
-
- // User defined group name for pinner accounting
- String groupName = "";
- ArrayList<PinnedFile> pinnedDeps = new ArrayList<>();
-
- PinnedFile(long address, int mapSize, String fileName, int bytesPinned) {
- mAddress = address;
- this.mapSize = mapSize;
- this.fileName = fileName;
- this.bytesPinned = bytesPinned;
- }
-
- @Override
- public void close() {
- if (mAddress >= 0) {
- safeMunmap(mAddress, mapSize);
- mAddress = -1;
- }
- for (PinnedFile dep : pinnedDeps) {
- if (dep != null) {
- dep.close();
- }
- }
- }
-
- @Override
- public void finalize() {
- close();
- }
- }
-
final static class PinRange {
int start;
int length;
@@ -1528,7 +1469,6 @@ public final class PinnerService extends SystemService {
* Represents an app that was pinned.
*/
private final class PinnedApp {
-
/**
* The uid of the package being pinned. This stays constant while the package stays
* installed.
@@ -1557,11 +1497,9 @@ public final class PinnerService extends SystemService {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
- case PIN_ONSTART_MSG:
- {
+ case PIN_ONSTART_MSG: {
handlePinOnStart();
- }
- break;
+ } break;
default:
super.handleMessage(msg);
diff --git a/services/core/java/com/android/server/pinner/PinnerUtils.java b/services/core/java/com/android/server/pinner/PinnerUtils.java
new file mode 100644
index 000000000000..a836a83dedab
--- /dev/null
+++ b/services/core/java/com/android/server/pinner/PinnerUtils.java
@@ -0,0 +1,75 @@
+/*
+ * 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.pinner;
+
+import android.annotation.Nullable;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
+import android.util.Slog;
+
+import java.io.Closeable;
+import java.io.FileDescriptor;
+import java.io.IOException;
+
+/* package */ final class PinnerUtils {
+ private static final String TAG = "PinnerUtils";
+
+ public static long clamp(long min, long value, long max) {
+ return Math.max(min, Math.min(value, max));
+ }
+
+ public static void safeMunmap(long address, long mapSize) {
+ try {
+ Os.munmap(address, mapSize);
+ } catch (ErrnoException ex) {
+ Slog.w(TAG, "ignoring error in unmap", ex);
+ }
+ }
+
+ /**
+ * Close FD, swallowing irrelevant errors.
+ */
+ public static void safeClose(@Nullable FileDescriptor fd) {
+ if (fd != null && fd.valid()) {
+ try {
+ Os.close(fd);
+ } catch (ErrnoException ex) {
+ // Swallow the exception: non-EBADF errors in close(2)
+ // indicate deferred paging write errors, which we
+ // don't care about here. The underlying file
+ // descriptor is always closed.
+ if (ex.errno == OsConstants.EBADF) {
+ throw new AssertionError(ex);
+ }
+ }
+ }
+ }
+
+ /**
+ * Close closeable thing, swallowing errors.
+ */
+ public static void safeClose(@Nullable Closeable thing) {
+ if (thing != null) {
+ try {
+ thing.close();
+ } catch (IOException ex) {
+ Slog.w(TAG, "ignoring error closing resource: " + thing, ex);
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java
index 9ecc7b9a805d..1569fa0aa8d7 100644
--- a/services/core/java/com/android/server/pm/DexOptHelper.java
+++ b/services/core/java/com/android/server/pm/DexOptHelper.java
@@ -70,13 +70,13 @@ import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.LocalManagerRegistry;
import com.android.server.LocalServices;
-import com.android.server.PinnerService;
import com.android.server.art.ArtManagerLocal;
import com.android.server.art.DexUseManagerLocal;
import com.android.server.art.ReasonMapping;
import com.android.server.art.model.ArtFlags;
import com.android.server.art.model.DexoptParams;
import com.android.server.art.model.DexoptResult;
+import com.android.server.pinner.PinnerService;
import com.android.server.pm.PackageDexOptimizer.DexOptResult;
import com.android.server.pm.dex.DexManager;
import com.android.server.pm.dex.DexoptOptions;
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 1316df16027f..b1b1637c890b 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -50,6 +50,7 @@ import android.app.PendingIntent;
import android.app.admin.DevicePolicyEventLogger;
import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyManagerInternal;
+import android.app.role.RoleManager;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
@@ -201,6 +202,9 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
Manifest.permission.USE_FULL_SCREEN_INTENT
);
+ private static final String ROLE_SYSTEM_APP_PROTECTION_SERVICE =
+ "android.app.role.SYSTEM_APP_PROTECTION_SERVICE";
+
final PackageArchiver mPackageArchiver;
private final Context mContext;
@@ -1454,6 +1458,12 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
.createEvent(DevicePolicyEnums.UNINSTALL_PACKAGE)
.setAdmin(callerPackageName)
.write();
+ } else if (isSystemAppProtectionRoleHolder(snapshot, userId, callingUid)) {
+ // Allow the SYSTEM_APP_PROTECTION_SERVICE role holder to silently uninstall, with a
+ // clean calling identity to get DELETE_PACKAGES permission
+ Binder.withCleanCallingIdentity(() ->
+ mPm.deletePackageVersioned(versionedPackage, adapter.getBinder(), userId, flags)
+ );
} else {
ApplicationInfo appInfo = snapshot.getApplicationInfo(callerPackageName, 0, userId);
if (appInfo.targetSdkVersion >= Build.VERSION_CODES.P) {
@@ -1475,6 +1485,29 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
}
}
+ private Boolean isSystemAppProtectionRoleHolder(
+ @NonNull Computer snapshot, int userId, int callingUid) {
+ if (!Flags.deletePackagesSilentlyBackport()) {
+ return false;
+ }
+ String holderPackageName = Binder.withCleanCallingIdentity(() -> {
+ RoleManager roleManager = mPm.mContext.getSystemService(RoleManager.class);
+ if (roleManager == null) {
+ return null;
+ }
+ List<String> holders = roleManager.getRoleHoldersAsUser(
+ ROLE_SYSTEM_APP_PROTECTION_SERVICE, UserHandle.of(userId));
+ if (holders.isEmpty()) {
+ return null;
+ }
+ return holders.get(0);
+ });
+ if (holderPackageName == null) {
+ return false;
+ }
+ return snapshot.getPackageUid(holderPackageName, /* flags= */ 0, userId) == callingUid;
+ }
+
@Override
public void uninstallExistingPackage(VersionedPackage versionedPackage,
String callerPackageName, IntentSender statusReceiver, int userId) {
diff --git a/services/core/java/com/android/server/power/stats/wakeups/CpuWakeupStats.java b/services/core/java/com/android/server/power/stats/wakeups/CpuWakeupStats.java
index f047f564538d..ab630eef4644 100644
--- a/services/core/java/com/android/server/power/stats/wakeups/CpuWakeupStats.java
+++ b/services/core/java/com/android/server/power/stats/wakeups/CpuWakeupStats.java
@@ -17,6 +17,7 @@
package com.android.server.power.stats.wakeups;
import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_ALARM;
+import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_BLUETOOTH;
import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_CELLULAR_DATA;
import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_SENSOR;
import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER;
@@ -63,6 +64,7 @@ public class CpuWakeupStats {
private static final String SUBSYSTEM_SOUND_TRIGGER_STRING = "Sound_trigger";
private static final String SUBSYSTEM_SENSOR_STRING = "Sensor";
private static final String SUBSYSTEM_CELLULAR_DATA_STRING = "Cellular_data";
+ private static final String SUBSYSTEM_BLUETOOTH_STRING = "Bluetooth";
private static final String TRACE_TRACK_WAKEUP_ATTRIBUTION = "wakeup_attribution";
private static final long WAKEUP_WRITE_DELAY_MS = TimeUnit.SECONDS.toMillis(30);
@@ -512,6 +514,8 @@ public class CpuWakeupStats {
return CPU_WAKEUP_SUBSYSTEM_SENSOR;
case SUBSYSTEM_CELLULAR_DATA_STRING:
return CPU_WAKEUP_SUBSYSTEM_CELLULAR_DATA;
+ case SUBSYSTEM_BLUETOOTH_STRING:
+ return CPU_WAKEUP_SUBSYSTEM_BLUETOOTH;
}
return CPU_WAKEUP_SUBSYSTEM_UNKNOWN;
}
@@ -528,6 +532,8 @@ public class CpuWakeupStats {
return SUBSYSTEM_SENSOR_STRING;
case CPU_WAKEUP_SUBSYSTEM_CELLULAR_DATA:
return SUBSYSTEM_CELLULAR_DATA_STRING;
+ case CPU_WAKEUP_SUBSYSTEM_BLUETOOTH:
+ return SUBSYSTEM_BLUETOOTH_STRING;
case CPU_WAKEUP_SUBSYSTEM_UNKNOWN:
return "Unknown";
}
diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index f78c4488cbfb..d206c66ed09a 100644
--- a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -99,6 +99,7 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve
// True if needing to roll back only rebootless apexes when native crash happens
private boolean mTwoPhaseRollbackEnabled;
+ /** @hide */
@VisibleForTesting
public RollbackPackageHealthObserver(Context context, ApexManager apexManager) {
mContext = context;
@@ -123,7 +124,7 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve
}
}
- RollbackPackageHealthObserver(Context context) {
+ public RollbackPackageHealthObserver(@NonNull Context context) {
this(context, ApexManager.getInstance());
}
@@ -239,8 +240,8 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve
return false;
}
-
@Override
+ @NonNull
public String getUniqueIdentifier() {
return NAME;
}
@@ -251,7 +252,7 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve
}
@Override
- public boolean mayObservePackage(String packageName) {
+ public boolean mayObservePackage(@NonNull String packageName) {
if (getAvailableRollbacks().isEmpty()) {
return false;
}
@@ -281,12 +282,14 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve
* This may cause {@code packages} to be rolled back if they crash too freqeuntly.
*/
@AnyThread
- void startObservingHealth(List<String> packages, long durationMs) {
+ @NonNull
+ public void startObservingHealth(@NonNull List<String> packages, @NonNull long durationMs) {
PackageWatchdog.getInstance(mContext).startObservingHealth(this, packages, durationMs);
}
@AnyThread
- void notifyRollbackAvailable(RollbackInfo rollback) {
+ @NonNull
+ public void notifyRollbackAvailable(@NonNull RollbackInfo rollback) {
mHandler.post(() -> {
// Enable two-phase rollback when a rebootless apex rollback is made available.
// We assume the rebootless apex is stable and is less likely to be the cause
@@ -314,7 +317,7 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve
* to check for native crashes and mitigate them if needed.
*/
@AnyThread
- void onBootCompletedAsync() {
+ public void onBootCompletedAsync() {
mHandler.post(()->onBootCompleted());
}
diff --git a/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java b/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java
index 79560ce27919..9cfed02f9355 100644
--- a/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java
+++ b/services/core/java/com/android/server/rollback/WatchdogRollbackLogger.java
@@ -51,6 +51,7 @@ import java.util.List;
/**
* This class handles the logic for logging Watchdog-triggered rollback events.
+ * @hide
*/
public final class WatchdogRollbackLogger {
private static final String TAG = "WatchdogRollbackLogger";
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index b35a0a772ff2..74c1124e1f16 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -216,13 +216,13 @@ import com.android.role.RoleManagerLocal;
import com.android.server.BinderCallsStatsService;
import com.android.server.LocalManagerRegistry;
import com.android.server.LocalServices;
-import com.android.server.PinnerService;
-import com.android.server.PinnerService.PinnedFileStats;
import com.android.server.SystemService;
import com.android.server.SystemServiceManager;
import com.android.server.am.MemoryStatUtil.MemoryStat;
import com.android.server.health.HealthServiceWrapper;
import com.android.server.notification.NotificationManagerService;
+import com.android.server.pinner.PinnerService;
+import com.android.server.pinner.PinnerService.PinnedFileStats;
import com.android.server.pm.UserManagerInternal;
import com.android.server.power.stats.KernelWakelockReader;
import com.android.server.power.stats.KernelWakelockStats;
diff --git a/services/core/java/com/android/server/vibrator/VibratorController.java b/services/core/java/com/android/server/vibrator/VibratorController.java
index c120fc7d82f5..6aed00e0e32b 100644
--- a/services/core/java/com/android/server/vibrator/VibratorController.java
+++ b/services/core/java/com/android/server/vibrator/VibratorController.java
@@ -57,8 +57,7 @@ final class VibratorController {
// for a snippet of the current known vibrator state/info.
private volatile VibratorInfo mVibratorInfo;
private volatile boolean mVibratorInfoLoadSuccessful;
- private volatile boolean mIsVibrating;
- private volatile boolean mIsUnderExternalControl;
+ private volatile VibratorState mCurrentState;
private volatile float mCurrentAmplitude;
/**
@@ -75,6 +74,11 @@ final class VibratorController {
void onComplete(int vibratorId, long vibrationId);
}
+ /** Representation of the vibrator state based on the interactions through this controller. */
+ private enum VibratorState {
+ IDLE, VIBRATING, UNDER_EXTERNAL_CONTROL
+ }
+
VibratorController(int vibratorId, OnVibrationCompleteListener listener) {
this(vibratorId, listener, new NativeWrapper());
}
@@ -87,6 +91,7 @@ final class VibratorController {
VibratorInfo.Builder vibratorInfoBuilder = new VibratorInfo.Builder(vibratorId);
mVibratorInfoLoadSuccessful = mNativeWrapper.getInfo(vibratorInfoBuilder);
mVibratorInfo = vibratorInfoBuilder.build();
+ mCurrentState = VibratorState.IDLE;
if (!mVibratorInfoLoadSuccessful) {
Slog.e(TAG,
@@ -106,7 +111,7 @@ final class VibratorController {
return false;
}
// Notify its callback after new client registered.
- notifyStateListener(listener, mIsVibrating);
+ notifyStateListener(listener, isVibrating(mCurrentState));
}
return true;
} finally {
@@ -166,7 +171,7 @@ final class VibratorController {
* automatically notified to any registered {@link IVibratorStateListener} on change.
*/
public boolean isVibrating() {
- return mIsVibrating;
+ return isVibrating(mCurrentState);
}
/**
@@ -184,11 +189,6 @@ final class VibratorController {
return mCurrentAmplitude;
}
- /** Return {@code true} if this vibrator is under external control, false otherwise. */
- public boolean isUnderExternalControl() {
- return mIsUnderExternalControl;
- }
-
/**
* Check against this vibrator capabilities.
*
@@ -214,7 +214,7 @@ final class VibratorController {
/**
* Set the vibrator control to be external or not, based on given flag.
*
- * <p>This will affect the state of {@link #isUnderExternalControl()}.
+ * <p>This will affect the state of {@link #isVibrating()}.
*/
public void setExternalControl(boolean externalControl) {
Trace.traceBegin(TRACE_TAG_VIBRATOR,
@@ -224,9 +224,11 @@ final class VibratorController {
if (!mVibratorInfo.hasCapability(IVibrator.CAP_EXTERNAL_CONTROL)) {
return;
}
+ VibratorState newState =
+ externalControl ? VibratorState.UNDER_EXTERNAL_CONTROL : VibratorState.IDLE;
synchronized (mLock) {
- mIsUnderExternalControl = externalControl;
mNativeWrapper.setExternalControl(externalControl);
+ updateStateAndNotifyListenersLocked(newState);
}
} finally {
Trace.traceEnd(TRACE_TAG_VIBRATOR);
@@ -264,7 +266,7 @@ final class VibratorController {
if (mVibratorInfo.hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL)) {
mNativeWrapper.setAmplitude(amplitude);
}
- if (mIsVibrating) {
+ if (mCurrentState == VibratorState.VIBRATING) {
mCurrentAmplitude = amplitude;
}
}
@@ -289,7 +291,7 @@ final class VibratorController {
long duration = mNativeWrapper.on(milliseconds, vibrationId);
if (duration > 0) {
mCurrentAmplitude = -1;
- notifyListenerOnVibrating(true);
+ updateStateAndNotifyListenersLocked(VibratorState.VIBRATING);
}
return duration;
}
@@ -319,7 +321,7 @@ final class VibratorController {
vendorEffect.getAdaptiveScale(), vibrationId);
if (duration > 0) {
mCurrentAmplitude = -1;
- notifyListenerOnVibrating(true);
+ updateStateAndNotifyListenersLocked(VibratorState.VIBRATING);
}
return duration;
} finally {
@@ -346,7 +348,7 @@ final class VibratorController {
prebaked.getEffectStrength(), vibrationId);
if (duration > 0) {
mCurrentAmplitude = -1;
- notifyListenerOnVibrating(true);
+ updateStateAndNotifyListenersLocked(VibratorState.VIBRATING);
}
return duration;
}
@@ -374,7 +376,7 @@ final class VibratorController {
long duration = mNativeWrapper.compose(primitives, vibrationId);
if (duration > 0) {
mCurrentAmplitude = -1;
- notifyListenerOnVibrating(true);
+ updateStateAndNotifyListenersLocked(VibratorState.VIBRATING);
}
return duration;
}
@@ -402,7 +404,7 @@ final class VibratorController {
long duration = mNativeWrapper.composePwle(primitives, braking, vibrationId);
if (duration > 0) {
mCurrentAmplitude = -1;
- notifyListenerOnVibrating(true);
+ updateStateAndNotifyListenersLocked(VibratorState.VIBRATING);
}
return duration;
}
@@ -422,7 +424,7 @@ final class VibratorController {
synchronized (mLock) {
mNativeWrapper.off();
mCurrentAmplitude = 0;
- notifyListenerOnVibrating(false);
+ updateStateAndNotifyListenersLocked(VibratorState.IDLE);
}
} finally {
Trace.traceEnd(TRACE_TAG_VIBRATOR);
@@ -443,9 +445,8 @@ final class VibratorController {
return "VibratorController{"
+ "mVibratorInfo=" + mVibratorInfo
+ ", mVibratorInfoLoadSuccessful=" + mVibratorInfoLoadSuccessful
- + ", mIsVibrating=" + mIsVibrating
+ + ", mCurrentState=" + mCurrentState.name()
+ ", mCurrentAmplitude=" + mCurrentAmplitude
- + ", mIsUnderExternalControl=" + mIsUnderExternalControl
+ ", mVibratorStateListeners count="
+ mVibratorStateListeners.getRegisteredCallbackCount()
+ '}';
@@ -454,8 +455,7 @@ final class VibratorController {
void dump(IndentingPrintWriter pw) {
pw.println("Vibrator (id=" + mVibratorInfo.getId() + "):");
pw.increaseIndent();
- pw.println("isVibrating = " + mIsVibrating);
- pw.println("isUnderExternalControl = " + mIsUnderExternalControl);
+ pw.println("currentState = " + mCurrentState.name());
pw.println("currentAmplitude = " + mCurrentAmplitude);
pw.println("vibratorInfoLoadSuccessful = " + mVibratorInfoLoadSuccessful);
pw.println("vibratorStateListener size = "
@@ -464,14 +464,19 @@ final class VibratorController {
pw.decreaseIndent();
}
+ /**
+ * Updates current vibrator state and notify listeners if {@link #isVibrating()} result changed.
+ */
@GuardedBy("mLock")
- private void notifyListenerOnVibrating(boolean isVibrating) {
- if (mIsVibrating != isVibrating) {
- mIsVibrating = isVibrating;
+ private void updateStateAndNotifyListenersLocked(VibratorState state) {
+ boolean previousIsVibrating = isVibrating(mCurrentState);
+ final boolean newIsVibrating = isVibrating(state);
+ mCurrentState = state;
+ if (previousIsVibrating != newIsVibrating) {
// The broadcast method is safe w.r.t. register/unregister listener methods, but lock
// is required here to guarantee delivery order.
mVibratorStateListeners.broadcast(
- listener -> notifyStateListener(listener, isVibrating));
+ listener -> notifyStateListener(listener, newIsVibrating));
}
}
@@ -483,6 +488,11 @@ final class VibratorController {
}
}
+ /** Returns true only if given state is not {@link VibratorState#IDLE}. */
+ private static boolean isVibrating(VibratorState state) {
+ return state != VibratorState.IDLE;
+ }
+
/** Wrapper around the static-native methods of {@link VibratorController} for tests. */
@VisibleForTesting
public static class NativeWrapper {
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 95c648334327..07473d10b217 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -809,17 +809,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
mCurrentExternalVibration.getDebugInfo().dump(proto,
VibratorManagerServiceDumpProto.CURRENT_EXTERNAL_VIBRATION);
}
-
- boolean isVibrating = false;
- boolean isUnderExternalControl = false;
for (int i = 0; i < mVibrators.size(); i++) {
proto.write(VibratorManagerServiceDumpProto.VIBRATOR_IDS, mVibrators.keyAt(i));
- isVibrating |= mVibrators.valueAt(i).isVibrating();
- isUnderExternalControl |= mVibrators.valueAt(i).isUnderExternalControl();
}
- proto.write(VibratorManagerServiceDumpProto.IS_VIBRATING, isVibrating);
- proto.write(VibratorManagerServiceDumpProto.VIBRATOR_UNDER_EXTERNAL_CONTROL,
- isUnderExternalControl);
}
mVibratorManagerRecords.dump(proto);
mVibratorControlService.dump(proto);
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 4754ffb5cf6e..2d75f35d2a9c 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -20,6 +20,7 @@ import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
import static android.Manifest.permission.MANAGE_EXTERNAL_STORAGE;
import static android.Manifest.permission.READ_WALLPAPER_INTERNAL;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
+import static android.app.Flags.fixWallpaperChanged;
import static android.app.Flags.removeNextWallpaperComponent;
import static android.app.WallpaperManager.COMMAND_REAPPLY;
import static android.app.WallpaperManager.FLAG_LOCK;
@@ -349,7 +350,10 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
if (DEBUG) {
Slog.d(TAG, "publish system wallpaper changed!");
}
- notifyWallpaperChanged(wallpaper);
+ notifyWallpaperComplete(wallpaper);
+ if (fixWallpaperChanged()) {
+ notifyWallpaperChanged(wallpaper);
+ }
}
};
@@ -369,7 +373,10 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
if (DEBUG) {
Slog.d(TAG, "publish lock wallpaper changed!");
}
- notifyWallpaperChanged(wallpaper);
+ notifyWallpaperComplete(wallpaper);
+ if (fixWallpaperChanged()) {
+ notifyWallpaperChanged(wallpaper);
+ }
}
};
@@ -403,8 +410,11 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
}
- private void notifyWallpaperChanged(WallpaperData wallpaper) {
- // Publish completion *after* we've persisted the changes
+ /*
+ * Calls wallpaper setComplete methods. Called for static wallpapers after the wallpaper is set
+ * and changes are persisted.
+ */
+ private void notifyWallpaperComplete(WallpaperData wallpaper) {
if (wallpaper.setComplete != null) {
try {
wallpaper.setComplete.onWallpaperChanged();
@@ -1468,7 +1478,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
|| change == PACKAGE_TEMPORARY_CHANGE) {
changed = true;
if (doit) {
- Slog.w(TAG, "Wallpaper uninstalled, removing: "
+ Slog.e(TAG, "Wallpaper uninstalled, removing: "
+ wallpaper.getComponent());
clearWallpaperLocked(wallpaper.mWhich, wallpaper.userId, false, null);
}
@@ -1491,7 +1501,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
PackageManager.MATCH_DIRECT_BOOT_AWARE
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
} catch (NameNotFoundException e) {
- Slog.w(TAG, "Wallpaper component gone, removing: "
+ Slog.e(TAG, "Wallpaper component gone, removing: "
+ wallpaper.getComponent());
clearWallpaperLocked(wallpaper.mWhich, wallpaper.userId, false, null);
}
@@ -1787,6 +1797,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
switchWallpaper(systemWallpaper, null);
// TODO(b/278261563): call notifyCallbacksLocked inside switchWallpaper
notifyCallbacksLocked(systemWallpaper);
+ if (fixWallpaperChanged()) {
+ notifyWallpaperChanged(systemWallpaper);
+ }
}
if (mLockWallpaperWaitingForUnlock) {
final WallpaperData lockWallpaper =
@@ -1794,6 +1807,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
lockWallpaper.mBindSource = BindSource.SWITCH_WALLPAPER_UNLOCK_USER;
switchWallpaper(lockWallpaper, null);
notifyCallbacksLocked(lockWallpaper);
+ if (fixWallpaperChanged()) {
+ notifyWallpaperChanged(lockWallpaper);
+ }
}
// Make sure that the SELinux labeling of all the relevant files is correct.
@@ -3248,6 +3264,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
newWallpaper.wallpaperId = makeWallpaperIdLocked();
notifyCallbacksLocked(newWallpaper);
+ if (fixWallpaperChanged()) {
+ notifyWallpaperChanged(newWallpaper);
+ }
shouldNotifyColors = true;
if (offloadColorExtraction()) {
shouldNotifyColors = false;
@@ -3316,6 +3335,21 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
return false;
}
+ /*
+ * Attempt to bind the wallpaper given by `componentName`, returning true on success otherwise
+ * false.
+ *
+ * When called, `wallpaper` is in a deliberately inconsistent state. Most fields have been
+ * updated to describe the desired wallpaper, but the ComponentName is not updated until
+ * binding is successful. This is required for maybeDetachWallpapers() to work correctly.
+ *
+ * The late update of the component field should cause multi-threading headaches with
+ * WallpaperConnection#onServiceConnected, but doesn't because onServiceConnected required
+ * `mLock` and `bindWallpaperComponentLocked` is always called with that lock, which prevents a
+ * race condition.
+ *
+ * This is a major motivation for making WallpaperData immutable per b/267170056.
+ */
boolean bindWallpaperComponentLocked(ComponentName componentName, boolean force,
boolean fromUser, WallpaperData wallpaper, IRemoteCallback reply) {
if (DEBUG_LIVE) {
@@ -3610,8 +3644,18 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
wallpaper.callbacks.finishBroadcast();
+ if (!fixWallpaperChanged()) {
+ final Intent intent = new Intent(Intent.ACTION_WALLPAPER_CHANGED);
+ intent.putExtra(WallpaperManager.EXTRA_FROM_FOREGROUND_APP,
+ wallpaper.fromForegroundApp);
+ mContext.sendBroadcastAsUser(intent, new UserHandle(mCurrentUserId));
+ }
+ }
+
+ private void notifyWallpaperChanged(WallpaperData wallpaper) {
final Intent intent = new Intent(Intent.ACTION_WALLPAPER_CHANGED);
intent.putExtra(WallpaperManager.EXTRA_FROM_FOREGROUND_APP, wallpaper.fromForegroundApp);
+ intent.putExtra(WallpaperManager.EXTRA_WHICH_WALLPAPER_CHANGED, wallpaper.mWhich);
mContext.sendBroadcastAsUser(intent, new UserHandle(mCurrentUserId));
}
diff --git a/services/core/java/com/android/server/webkit/SystemImpl.java b/services/core/java/com/android/server/webkit/SystemImpl.java
index 67401530763b..ab5316f46d78 100644
--- a/services/core/java/com/android/server/webkit/SystemImpl.java
+++ b/services/core/java/com/android/server/webkit/SystemImpl.java
@@ -41,7 +41,8 @@ import android.webkit.WebViewZygote;
import com.android.internal.util.XmlUtils;
import com.android.server.LocalServices;
-import com.android.server.PinnerService;
+import com.android.server.pinner.PinnedFile;
+import com.android.server.pinner.PinnerService;
import org.xmlpull.v1.XmlPullParserException;
@@ -318,8 +319,9 @@ public class SystemImpl implements SystemInterface {
if (webviewPinQuota <= 0) {
break;
}
- int bytesPinned = pinnerService.pinFile(apk, webviewPinQuota, appInfo, PIN_GROUP);
- webviewPinQuota -= bytesPinned;
+ PinnedFile pf = pinnerService.pinFile(
+ apk, webviewPinQuota, appInfo, PIN_GROUP, /*pinOptimizedDeps=*/true);
+ webviewPinQuota -= pf.bytesPinned;
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 12d733fc8c1a..ca93075f2925 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -2154,7 +2154,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
mAtmService.mPackageConfigPersister.updateConfigIfNeeded(this, mUserId, packageName);
- mActivityRecordInputSink = new ActivityRecordInputSink(this, sourceRecord);
+ final boolean appOptInTouchPassThrough =
+ options != null && options.isAllowPassThroughOnTouchOutside();
+ mActivityRecordInputSink = new ActivityRecordInputSink(
+ this, sourceRecord, appOptInTouchPassThrough);
mAppActivityEmbeddingSplitsEnabled = isAppActivityEmbeddingSplitsEnabled();
mAllowUntrustedEmbeddingStateSharing = getAllowUntrustedEmbeddingStateSharingProperty();
@@ -3171,14 +3174,23 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
return getWindowConfiguration().canReceiveKeys() && !mWaitForEnteringPinnedMode;
}
- boolean isResizeable() {
- return isResizeable(/* checkPictureInPictureSupport */ true);
+ /**
+ * Returns {@code true} if the fixed orientation, aspect ratio, resizability of this activity
+ * will be ignored.
+ */
+ boolean isUniversalResizeable() {
+ return mWmService.mConstants.mIgnoreActivityOrientationRequest
+ && info.applicationInfo.category != ApplicationInfo.CATEGORY_GAME
+ // If the user preference respects aspect ratio, then it becomes non-resizable.
+ && !mAppCompatController.getAppCompatOverrides().getAppCompatAspectRatioOverrides()
+ .shouldApplyUserMinAspectRatioOverride();
}
- boolean isResizeable(boolean checkPictureInPictureSupport) {
+ boolean isResizeable() {
return mAtmService.mForceResizableActivities
|| ActivityInfo.isResizeableMode(info.resizeMode)
- || (info.supportsPictureInPicture() && checkPictureInPictureSupport)
+ || info.supportsPictureInPicture()
+ || isUniversalResizeable()
// If the activity can be embedded, it should inherit the bounds of task fragment.
|| isEmbedded();
}
@@ -6397,7 +6409,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// and the token could be null.
return;
}
- r.mDisplayContent.mAppCompatCameraPolicy.onActivityRefreshed(r);
+ final AppCompatCameraPolicy cameraPolicy = AppCompatCameraPolicy
+ .getAppCompatCameraPolicy(r);
+ if (cameraPolicy != null) {
+ cameraPolicy.onActivityRefreshed(r);
+ }
}
static void splashScreenAttachedLocked(IBinder token) {
@@ -8162,11 +8178,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
@Override
@ActivityInfo.ScreenOrientation
protected int getOverrideOrientation() {
- final int candidateOrientation;
- if (!mWmService.mConstants.mIgnoreActivityOrientationRequest
- || info.applicationInfo.category == ApplicationInfo.CATEGORY_GAME) {
- candidateOrientation = super.getOverrideOrientation();
- } else {
+ int candidateOrientation = super.getOverrideOrientation();
+ if (isUniversalResizeable() && ActivityInfo.isFixedOrientation(candidateOrientation)) {
candidateOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
}
return mAppCompatController.getOrientationPolicy()
@@ -9442,8 +9455,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
if (!shouldBeResumed(/* activeActivity */ null)) {
return;
}
- mDisplayContent.mAppCompatCameraPolicy.onActivityConfigurationChanging(
- this, newConfig, lastReportedConfig);
+
+ final AppCompatCameraPolicy cameraPolicy = AppCompatCameraPolicy.getAppCompatCameraPolicy(
+ this);
+ if (cameraPolicy != null) {
+ cameraPolicy.onActivityConfigurationChanging(this, newConfig, lastReportedConfig);
+ }
}
/** Get process configuration, or global config if the process is not set. */
@@ -10025,7 +10042,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
StringBuilder sb = new StringBuilder(128);
sb.append("ActivityRecord{");
- sb.append(Integer.toHexString(System.identityHashCode(this)));
+ sb.append(System.identityHashCode(this));
sb.append(" u");
sb.append(mUserId);
sb.append(' ');
diff --git a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java
index 1a197875ba31..fa5beca31ec1 100644
--- a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java
+++ b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java
@@ -16,13 +16,18 @@
package com.android.server.wm;
+import android.app.ActivityOptions;
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
+import android.os.Build;
import android.os.InputConfig;
import android.view.InputWindowHandle;
import android.view.SurfaceControl;
import android.view.WindowManager;
+import com.android.window.flags.Flags;
+
/**
* Creates a InputWindowHandle that catches all touches that would otherwise pass through an
* Activity.
@@ -35,6 +40,21 @@ class ActivityRecordInputSink {
@ChangeId
static final long ENABLE_TOUCH_OPAQUE_ACTIVITIES = 194480991L;
+ // TODO(b/369605358) Update EnabledSince when SDK 36 version code is available.
+ /**
+ * If the app's target SDK is 36+, pass-through touches from a cross-uid overlaying activity is
+ * blocked by default. The activity may opt in to receive pass-through touches using
+ * {@link ActivityOptions#setAllowPassThroughOnTouchOutside}, which allows the to-be-launched
+ * cross-uid overlaying activity and other activities in that app to pass through touches. The
+ * activity needs to ensure that it trusts the overlaying app and its content is not vulnerable
+ * to UI redressing attacks.
+ *
+ * @see ActivityOptions#setAllowPassThroughOnTouchOutside
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT)
+ static final long ENABLE_OVERLAY_TOUCH_PASS_THROUGH_OPT_IN_ENFORCEMENT = 358129114L;
+
private final ActivityRecord mActivityRecord;
private final boolean mIsCompatEnabled;
private final String mName;
@@ -42,13 +62,24 @@ class ActivityRecordInputSink {
private InputWindowHandleWrapper mInputWindowHandleWrapper;
private SurfaceControl mSurfaceControl;
- ActivityRecordInputSink(ActivityRecord activityRecord, ActivityRecord sourceRecord) {
+ ActivityRecordInputSink(ActivityRecord activityRecord, ActivityRecord sourceRecord,
+ boolean appOptInTouchPassThrough) {
mActivityRecord = activityRecord;
mIsCompatEnabled = CompatChanges.isChangeEnabled(ENABLE_TOUCH_OPAQUE_ACTIVITIES,
mActivityRecord.getUid());
mName = Integer.toHexString(System.identityHashCode(this)) + " ActivityRecordInputSink "
+ mActivityRecord.mActivityComponent.flattenToShortString();
- if (sourceRecord != null) {
+
+ if (sourceRecord == null) {
+ return;
+ }
+ // If the source activity has target sdk 36+, it is required to opt in to receive
+ // pass-through touches from the overlaying activity.
+ final boolean isTouchPassThroughOptInEnforced = CompatChanges.isChangeEnabled(
+ ENABLE_OVERLAY_TOUCH_PASS_THROUGH_OPT_IN_ENFORCEMENT,
+ sourceRecord.getUid());
+ if (!Flags.touchPassThroughOptIn() || !isTouchPassThroughOptInEnforced
+ || appOptInTouchPassThrough) {
sourceRecord.mAllowedTouchUid = mActivityRecord.getUid();
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index d29ff540e391..2ba300a71e38 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -100,6 +100,7 @@ import android.app.ProfilerInfo;
import android.app.WaitResult;
import android.app.WindowConfiguration;
import android.compat.annotation.ChangeId;
+import android.compat.annotation.Disabled;
import android.compat.annotation.EnabledSince;
import android.content.IIntentSender;
import android.content.Intent;
@@ -182,7 +183,7 @@ class ActivityStarter {
* Feature flag for go/activity-security rules
*/
@ChangeId
- @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ @Disabled
static final long ASM_RESTRICTIONS = 230590090L;
private final ActivityTaskManagerService mService;
@@ -1028,6 +1029,7 @@ class ActivityStarter {
if (requestCode >= 0 && !sourceRecord.finishing) {
resultRecord = sourceRecord;
}
+ request.logMessage.append(" (sr=" + System.identityHashCode(sourceRecord) + ")");
}
}
diff --git a/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java b/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
index f245efd7ff0e..0e666296dc33 100644
--- a/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
@@ -255,8 +255,8 @@ class AppCompatAspectRatioOverrides {
mActivityRecord.getOverrideOrientation());
final AppCompatCameraOverrides cameraOverrides =
mActivityRecord.mAppCompatController.getAppCompatCameraOverrides();
- final AppCompatCameraPolicy cameraPolicy =
- mActivityRecord.mAppCompatController.getAppCompatCameraPolicy();
+ final AppCompatCameraPolicy cameraPolicy = AppCompatCameraPolicy.getAppCompatCameraPolicy(
+ mActivityRecord);
// Don't resize to split screen size when in book mode if letterbox position is centered
return (isBookMode && isNotCenteredHorizontally || isTabletopMode && isLandscape)
|| cameraOverrides.isCameraCompatSplitScreenAspectRatioAllowed()
diff --git a/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
index 51ef87dcab1b..3b023fe451bf 100644
--- a/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
@@ -72,6 +72,15 @@ class AppCompatAspectRatioPolicy {
float getDesiredAspectRatio(@NonNull Configuration newParentConfig,
@NonNull Rect parentBounds) {
+ // If in camera compat mode, aspect ratio from the camera compat policy has priority over
+ // default letterbox aspect ratio.
+ final AppCompatCameraPolicy cameraPolicy = AppCompatCameraPolicy.getAppCompatCameraPolicy(
+ mActivityRecord);
+ if (cameraPolicy != null && cameraPolicy.shouldCameraCompatControlAspectRatio(
+ mActivityRecord)) {
+ return cameraPolicy.getCameraCompatAspectRatio(mActivityRecord);
+ }
+
final float letterboxAspectRatioOverride =
mAppCompatOverrides.getAppCompatAspectRatioOverrides()
.getFixedOrientationLetterboxAspectRatio(newParentConfig);
@@ -114,20 +123,20 @@ class AppCompatAspectRatioPolicy {
return mTransparentPolicy.getInheritedMinAspectRatio();
}
final ActivityInfo info = mActivityRecord.info;
- if (info.applicationInfo == null) {
- return info.getMinAspectRatio();
- }
final AppCompatAspectRatioOverrides aspectRatioOverrides =
mAppCompatOverrides.getAppCompatAspectRatioOverrides();
if (aspectRatioOverrides.shouldApplyUserMinAspectRatioOverride()) {
return aspectRatioOverrides.getUserMinAspectRatio();
}
- final DisplayContent displayContent = mActivityRecord.getDisplayContent();
- final boolean shouldOverrideMinAspectRatioForCamera = displayContent != null
- && displayContent.mAppCompatCameraPolicy.shouldOverrideMinAspectRatioForCamera(
- mActivityRecord);
+ final AppCompatCameraPolicy cameraPolicy = AppCompatCameraPolicy.getAppCompatCameraPolicy(
+ mActivityRecord);
+ final boolean shouldOverrideMinAspectRatioForCamera = cameraPolicy != null
+ && cameraPolicy.shouldOverrideMinAspectRatioForCamera(mActivityRecord);
if (!aspectRatioOverrides.shouldOverrideMinAspectRatio()
&& !shouldOverrideMinAspectRatioForCamera) {
+ if (mActivityRecord.isUniversalResizeable()) {
+ return 0;
+ }
return info.getMinAspectRatio();
}
@@ -170,6 +179,9 @@ class AppCompatAspectRatioPolicy {
if (mTransparentPolicy.isRunning()) {
return mTransparentPolicy.getInheritedMaxAspectRatio();
}
+ if (mActivityRecord.isUniversalResizeable()) {
+ return 0;
+ }
return mActivityRecord.info.getMaxAspectRatio();
}
diff --git a/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java b/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java
index 241390c12818..fbf9478b4fd9 100644
--- a/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java
@@ -170,7 +170,7 @@ class AppCompatCameraOverrides {
* </ul>
*/
boolean shouldApplyFreeformTreatmentForCameraCompat() {
- return Flags.cameraCompatForFreeform() && !isChangeEnabled(mActivityRecord,
+ return Flags.enableCameraCompatForDesktopWindowing() && !isChangeEnabled(mActivityRecord,
OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT);
}
diff --git a/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java b/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java
index 67bfd7605128..f6090eb89345 100644
--- a/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java
@@ -18,6 +18,8 @@ package com.android.server.wm;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static com.android.server.wm.AppCompatConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.pm.ActivityInfo.ScreenOrientation;
@@ -48,8 +50,9 @@ class AppCompatCameraPolicy {
// without the need to restart the device.
final boolean needsDisplayRotationCompatPolicy =
wmService.mAppCompatConfiguration.isCameraCompatTreatmentEnabledAtBuildTime();
- final boolean needsCameraCompatFreeformPolicy = Flags.cameraCompatForFreeform()
- && DesktopModeHelper.canEnterDesktopMode(wmService.mContext);
+ final boolean needsCameraCompatFreeformPolicy =
+ Flags.enableCameraCompatForDesktopWindowing()
+ && DesktopModeHelper.canEnterDesktopMode(wmService.mContext);
if (needsDisplayRotationCompatPolicy || needsCameraCompatFreeformPolicy) {
mCameraStateMonitor = new CameraStateMonitor(displayContent, wmService.mH);
mActivityRefresher = new ActivityRefresher(wmService, wmService.mH);
@@ -73,6 +76,12 @@ class AppCompatCameraPolicy {
}
}
+ @Nullable
+ static AppCompatCameraPolicy getAppCompatCameraPolicy(@NonNull ActivityRecord activityRecord) {
+ return activityRecord.mDisplayContent != null
+ ? activityRecord.mDisplayContent.mAppCompatCameraPolicy : null;
+ }
+
/**
* "Refreshes" activity by going through "stopped -> resumed" or "paused -> resumed" cycle.
* This allows to clear cached values in apps (e.g. display or camera rotation) that influence
@@ -166,12 +175,37 @@ class AppCompatCameraPolicy {
: SCREEN_ORIENTATION_UNSPECIFIED;
}
+ // TODO(b/369070416): have policies implement the same interface.
+ boolean shouldCameraCompatControlOrientation(@NonNull ActivityRecord activity) {
+ return (mDisplayRotationCompatPolicy != null
+ && mDisplayRotationCompatPolicy.shouldCameraCompatControlOrientation(
+ activity))
+ || (mCameraCompatFreeformPolicy != null
+ && mCameraCompatFreeformPolicy.shouldCameraCompatControlOrientation(
+ activity));
+ }
+
+ // TODO(b/369070416): have policies implement the same interface.
+ boolean shouldCameraCompatControlAspectRatio(@NonNull ActivityRecord activity) {
+ return (mDisplayRotationCompatPolicy != null
+ && mDisplayRotationCompatPolicy.shouldCameraCompatControlAspectRatio(
+ activity))
+ || (mCameraCompatFreeformPolicy != null
+ && mCameraCompatFreeformPolicy.shouldCameraCompatControlAspectRatio(
+ activity));
+ }
+
+ // TODO(b/369070416): have policies implement the same interface.
/**
- * @return {@code true} if the Camera is active for the provided {@link ActivityRecord}.
+ * @return {@code true} if the Camera is active for the provided {@link ActivityRecord} and
+ * any camera compat treatment could be triggered for the current windowing mode.
*/
- boolean isCameraActive(@NonNull ActivityRecord activity, boolean mustBeFullscreen) {
- return mDisplayRotationCompatPolicy != null
- && mDisplayRotationCompatPolicy.isCameraActive(activity, mustBeFullscreen);
+ private boolean isCameraRunningAndWindowingModeEligible(@NonNull ActivityRecord activity) {
+ return (mDisplayRotationCompatPolicy != null
+ && mDisplayRotationCompatPolicy.isCameraRunningAndWindowingModeEligible(activity,
+ /* mustBeFullscreen */ true))
+ || (mCameraCompatFreeformPolicy != null && mCameraCompatFreeformPolicy
+ .isCameraRunningAndWindowingModeEligible(activity));
}
@Nullable
@@ -182,12 +216,24 @@ class AppCompatCameraPolicy {
return null;
}
+ // TODO(b/369070416): have policies implement the same interface.
+ float getCameraCompatAspectRatio(@NonNull ActivityRecord activity) {
+ float displayRotationCompatPolicyAspectRatio = mDisplayRotationCompatPolicy != null
+ ? mDisplayRotationCompatPolicy.getCameraCompatAspectRatio(activity)
+ : MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO;
+ float cameraCompatFreeformPolicyAspectRatio = mCameraCompatFreeformPolicy != null
+ ? mCameraCompatFreeformPolicy.getCameraCompatAspectRatio(activity)
+ : MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO;
+ return Math.max(displayRotationCompatPolicyAspectRatio,
+ cameraCompatFreeformPolicyAspectRatio);
+ }
+
/**
* Whether we should apply the min aspect ratio per-app override only when an app is connected
* to the camera.
*/
boolean shouldOverrideMinAspectRatioForCamera(@NonNull ActivityRecord activityRecord) {
- return isCameraActive(activityRecord, /* mustBeFullscreen= */ true)
+ return isCameraRunningAndWindowingModeEligible(activityRecord)
&& activityRecord.mAppCompatController.getAppCompatCameraOverrides()
.isOverrideMinAspectRatioForCameraEnabled();
}
diff --git a/services/core/java/com/android/server/wm/AppCompatConfiguration.java b/services/core/java/com/android/server/wm/AppCompatConfiguration.java
index 42378aaf6c05..38c6de146293 100644
--- a/services/core/java/com/android/server/wm/AppCompatConfiguration.java
+++ b/services/core/java/com/android/server/wm/AppCompatConfiguration.java
@@ -290,6 +290,10 @@ final class AppCompatConfiguration {
// is enabled and activity is connected to the camera in fullscreen.
private final boolean mIsCameraCompatSplitScreenAspectRatioEnabled;
+ // Which aspect ratio to use when camera compat treatment is enabled and an activity eligible
+ // for treatment is connected to the camera.
+ private float mCameraCompatAspectRatio;
+
// Whether activity "refresh" in camera compatibility treatment is enabled.
// See RefreshCallbackItem for context.
private boolean mIsCameraCompatTreatmentRefreshEnabled = true;
@@ -363,6 +367,8 @@ final class AppCompatConfiguration {
.config_letterboxIsDisplayAspectRatioForFixedOrientationLetterboxEnabled);
mIsCameraCompatSplitScreenAspectRatioEnabled = mContext.getResources().getBoolean(
R.bool.config_isWindowManagerCameraCompatSplitScreenAspectRatioEnabled);
+ mCameraCompatAspectRatio = mContext.getResources().getFloat(
+ R.dimen.config_windowManagerCameraCompatAspectRatio);
mIsPolicyForIgnoringRequestedOrientationEnabled = mContext.getResources().getBoolean(
R.bool.config_letterboxIsPolicyForIgnoringRequestedOrientationEnabled);
@@ -1320,6 +1326,31 @@ final class AppCompatConfiguration {
}
/**
+ * Overrides aspect ratio to use when camera compat treatment is enabled and an activity
+ * eligible for treatment is connected to the camera.
+ */
+ void setCameraCompatAspectRatio(float aspectRatio) {
+ mCameraCompatAspectRatio = aspectRatio;
+ }
+
+ /**
+ * Which aspect ratio to use when camera compat treatment is enabled and an activity eligible
+ * for treatment is connected to the camera.
+ */
+ float getCameraCompatAspectRatio() {
+ return mCameraCompatAspectRatio;
+ }
+
+ /**
+ * Resets aspect ratio to use when camera compat treatment is enabled and an activity eligible
+ * for treatment is connected to the camera.
+ */
+ void resetCameraCompatAspectRatio() {
+ mCameraCompatAspectRatio = mContext.getResources().getFloat(R.dimen
+ .config_windowManagerCameraCompatAspectRatio);
+ }
+
+ /**
* 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/AppCompatController.java b/services/core/java/com/android/server/wm/AppCompatController.java
index 173362c16728..6c344c6d850a 100644
--- a/services/core/java/com/android/server/wm/AppCompatController.java
+++ b/services/core/java/com/android/server/wm/AppCompatController.java
@@ -16,7 +16,6 @@
package com.android.server.wm;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.content.pm.PackageManager;
import com.android.server.wm.utils.OptPropFactory;
@@ -118,14 +117,6 @@ class AppCompatController {
return mAppCompatOverrides.getAppCompatResizeOverrides();
}
- @Nullable
- AppCompatCameraPolicy getAppCompatCameraPolicy() {
- if (mActivityRecord.mDisplayContent != null) {
- return mActivityRecord.mDisplayContent.mAppCompatCameraPolicy;
- }
- return null;
- }
-
@NonNull
AppCompatReachabilityPolicy getAppCompatReachabilityPolicy() {
return mAppCompatReachabilityPolicy;
diff --git a/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java b/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
index 7477c6272d89..5bd4aeb64b90 100644
--- a/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatOrientationPolicy.java
@@ -58,16 +58,17 @@ class AppCompatOrientationPolicy {
&& displayContent.getIgnoreOrientationRequest();
final boolean shouldApplyUserFullscreenOverride = mAppCompatOverrides
.getAppCompatAspectRatioOverrides().shouldApplyUserFullscreenOverride();
- final boolean isCameraActive = displayContent != null
- && displayContent.mAppCompatCameraPolicy.isCameraActive(mActivityRecord,
- /* mustBeFullscreen */ true);
+ final AppCompatCameraPolicy cameraPolicy = AppCompatCameraPolicy
+ .getAppCompatCameraPolicy(mActivityRecord);
+ final boolean shouldCameraCompatControlOrientation = cameraPolicy != null
+ && cameraPolicy.shouldCameraCompatControlOrientation(mActivityRecord);
if (shouldApplyUserFullscreenOverride && isIgnoreOrientationRequestEnabled
// Do not override orientation to fullscreen for camera activities.
// Fixed-orientation activities are rarely tested in other orientations, and it
// often results in sideways or stretched previews. As the camera compat treatment
// targets fixed-orientation activities, overriding the orientation disables the
// treatment.
- && !isCameraActive) {
+ && !shouldCameraCompatControlOrientation) {
Slog.v(TAG, "Requested orientation " + screenOrientationToString(candidate)
+ " for " + mActivityRecord + " is overridden to "
+ screenOrientationToString(SCREEN_ORIENTATION_USER)
@@ -113,7 +114,7 @@ class AppCompatOrientationPolicy {
// often results in sideways or stretched previews. As the camera compat treatment
// targets fixed-orientation activities, overriding the orientation disables the
// treatment.
- && !isCameraActive) {
+ && !shouldCameraCompatControlOrientation) {
Slog.v(TAG, "Requested orientation " + screenOrientationToString(candidate)
+ " for " + mActivityRecord + " is overridden to "
+ screenOrientationToString(SCREEN_ORIENTATION_USER));
@@ -192,8 +193,9 @@ class AppCompatOrientationPolicy {
+ mActivityRecord);
return true;
}
- final AppCompatCameraPolicy cameraPolicy = mActivityRecord.mAppCompatController
- .getAppCompatCameraPolicy();
+
+ final AppCompatCameraPolicy cameraPolicy = AppCompatCameraPolicy
+ .getAppCompatCameraPolicy(mActivityRecord);
if (cameraPolicy != null
&& cameraPolicy.isTreatmentEnabledForActivity(mActivityRecord)) {
Slog.w(TAG, "Ignoring orientation update to "
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 2259b5a5b08c..515f148ac2ff 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -1120,7 +1120,9 @@ public class BackgroundActivityStartController {
@Nullable Task targetTask, int launchFlags, int balCode, int callingUid,
int realCallingUid, TaskDisplayArea preferredTaskDisplayArea) {
// BAL Exception allowed in all cases
- if (balCode == BAL_ALLOW_ALLOWLISTED_UID) {
+ if (balCode == BAL_ALLOW_ALLOWLISTED_UID
+ || (android.security.Flags.asmReintroduceGracePeriod()
+ && balCode == BAL_ALLOW_GRACE_PERIOD)) {
return true;
}
@@ -1173,10 +1175,15 @@ public class BackgroundActivityStartController {
ArrayList<Task> visibleTasks = displayArea.getVisibleTasks();
for (int i = 0; i < visibleTasks.size(); i++) {
Task task = visibleTasks.get(i);
- if (visibleTasks.size() == 1 && task.isActivityTypeHomeOrRecents()) {
- bas.optedIn(task.getTopMostActivity());
- } else {
+ if (android.security.Flags.asmReintroduceGracePeriod()) {
bas = checkTopActivityForAsm(task, callingUid, /*sourceRecord*/null, bas);
+ } else {
+ if (visibleTasks.size() == 1 && task.isActivityTypeHomeOrRecents()) {
+ bas.optedIn(task.getTopMostActivity());
+ } else {
+ bas = checkTopActivityForAsm(
+ task, callingUid, /*sourceRecord*/null, bas);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java b/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java
index e3232e08749e..290e71d5b37d 100644
--- a/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java
+++ b/services/core/java/com/android/server/wm/CameraCompatFreeformPolicy.java
@@ -29,6 +29,7 @@ import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
import static android.view.Surface.ROTATION_0;
import static android.view.Surface.ROTATION_180;
+import static com.android.server.wm.AppCompatConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -123,8 +124,8 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa
* </ul>
*/
@VisibleForTesting
- boolean shouldApplyFreeformTreatmentForCameraCompat(@NonNull ActivityRecord activity) {
- return Flags.cameraCompatForFreeform() && !activity.info.isChangeEnabled(
+ boolean isCameraCompatForFreeformEnabledForActivity(@NonNull ActivityRecord activity) {
+ return Flags.enableCameraCompatForDesktopWindowing() && !activity.info.isChangeEnabled(
ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT);
}
@@ -170,6 +171,36 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa
return true;
}
+ boolean shouldCameraCompatControlOrientation(@NonNull ActivityRecord activity) {
+ return isCameraRunningAndWindowingModeEligible(activity);
+ }
+
+ boolean isCameraRunningAndWindowingModeEligible(@NonNull ActivityRecord activity) {
+ return activity.inFreeformWindowingMode()
+ && mCameraStateMonitor.isCameraRunningForActivity(activity);
+ }
+
+ boolean shouldCameraCompatControlAspectRatio(@NonNull ActivityRecord activity) {
+ // Camera compat should direct aspect ratio when in camera compat mode, unless an app has a
+ // different camera compat aspect ratio set: this allows per-app camera compat override
+ // aspect ratio to be smaller than the default.
+ return isInCameraCompatMode(activity) && !activity.mAppCompatController
+ .getAppCompatCameraOverrides().isOverrideMinAspectRatioForCameraEnabled();
+ }
+
+ private boolean isInCameraCompatMode(@NonNull ActivityRecord activity) {
+ return activity.mAppCompatController.getAppCompatCameraOverrides()
+ .getFreeformCameraCompatMode() != CAMERA_COMPAT_FREEFORM_NONE;
+ }
+
+ float getCameraCompatAspectRatio(@NonNull ActivityRecord activityRecord) {
+ if (shouldCameraCompatControlAspectRatio(activityRecord)) {
+ return activityRecord.mWmService.mAppCompatConfiguration.getCameraCompatAspectRatio();
+ }
+
+ return MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO;
+ }
+
private void forceUpdateActivityAndTask(ActivityRecord cameraActivity) {
cameraActivity.recomputeConfiguration();
cameraActivity.updateReportedConfigurationAndSend();
@@ -225,7 +256,7 @@ final class CameraCompatFreeformPolicy implements CameraStateMonitor.CameraCompa
*/
private boolean isTreatmentEnabledForActivity(@NonNull ActivityRecord activity) {
int orientation = activity.getRequestedConfigurationOrientation();
- return shouldApplyFreeformTreatmentForCameraCompat(activity)
+ return isCameraCompatForFreeformEnabledForActivity(activity)
&& mCameraStateMonitor.isCameraRunningForActivity(activity)
&& orientation != ORIENTATION_UNDEFINED
&& activity.inFreeformWindowingMode()
diff --git a/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java b/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java
index 192469183a54..3b2f723fb172 100644
--- a/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java
+++ b/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java
@@ -188,21 +188,21 @@ public class DesktopAppCompatAspectRatioPolicy {
}
final ActivityInfo info = mActivityRecord.info;
- if (info.applicationInfo == null) {
- return info.getMinAspectRatio();
- }
-
final AppCompatAspectRatioOverrides aspectRatioOverrides =
mAppCompatOverrides.getAppCompatAspectRatioOverrides();
if (shouldApplyUserMinAspectRatioOverride(task)) {
return aspectRatioOverrides.getUserMinAspectRatio();
}
- final DisplayContent dc = task.mDisplayContent;
- final boolean shouldOverrideMinAspectRatioForCamera = dc != null
- && dc.mAppCompatCameraPolicy.shouldOverrideMinAspectRatioForCamera(mActivityRecord);
+ final AppCompatCameraPolicy cameraPolicy = AppCompatCameraPolicy.getAppCompatCameraPolicy(
+ mActivityRecord);
+ final boolean shouldOverrideMinAspectRatioForCamera = cameraPolicy != null
+ && cameraPolicy.shouldOverrideMinAspectRatioForCamera(mActivityRecord);
if (!aspectRatioOverrides.shouldOverrideMinAspectRatio()
&& !shouldOverrideMinAspectRatioForCamera) {
+ if (mActivityRecord.isUniversalResizeable()) {
+ return 0;
+ }
return info.getMinAspectRatio();
}
@@ -246,6 +246,9 @@ public class DesktopAppCompatAspectRatioPolicy {
if (mTransparentPolicy.isRunning()) {
return mTransparentPolicy.getInheritedMaxAspectRatio();
}
+ if (mActivityRecord.isUniversalResizeable()) {
+ return 0;
+ }
return mActivityRecord.info.getMaxAspectRatio();
}
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index e6f6215b5f7f..1ac0bb0e41c6 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -2156,6 +2156,11 @@ public class DisplayPolicy {
}
mDecorInsets.invalidate();
mDecorInsets.mInfoForRotation[rotation].set(newInfo);
+ if (!mService.mDisplayEnabled) {
+ // There could be other pending changes during booting. It might be better to let the
+ // clients receive the new states earlier.
+ return true;
+ }
return !sameConfigFrame;
}
diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
index efc38439bfcf..90f8b4900c3f 100644
--- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
@@ -30,6 +30,7 @@ import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
import static android.view.Display.TYPE_INTERNAL;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
+import static com.android.server.wm.AppCompatConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO;
import static com.android.server.wm.DisplayRotationReversionController.REVERSION_TYPE_CAMERA_COMPAT;
import android.annotation.NonNull;
@@ -133,6 +134,11 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp
return mLastReportedOrientation;
}
+ float getCameraCompatAspectRatio(@NonNull ActivityRecord unusedActivity) {
+ // This policy does not apply camera compat aspect ratio by default, only via overrides.
+ return MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO;
+ }
+
@ScreenOrientation
private synchronized int getOrientationInternal() {
if (!isTreatmentEnabledForDisplay()) {
@@ -271,7 +277,7 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp
boolean isActivityEligibleForOrientationOverride(@NonNull ActivityRecord activity) {
return isTreatmentEnabledForDisplay()
- && isCameraActive(activity, /* mustBeFullscreen */ true)
+ && isCameraRunningAndWindowingModeEligible(activity, /* mustBeFullscreen */ true)
&& activity.mAppCompatController.getAppCompatCameraOverrides()
.shouldForceRotateForCameraCompat();
}
@@ -290,7 +296,17 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp
return isTreatmentEnabledForActivity(activity, /* mustBeFullscreen */ true);
}
- boolean isCameraActive(@NonNull ActivityRecord activity, boolean mustBeFullscreen) {
+ boolean shouldCameraCompatControlOrientation(@NonNull ActivityRecord activity) {
+ return isCameraRunningAndWindowingModeEligible(activity, /* mustBeFullscreen= */ true);
+ }
+
+ boolean shouldCameraCompatControlAspectRatio(@NonNull ActivityRecord unusedActivity) {
+ // This policy does not apply camera compat aspect ratio by default, only via overrides.
+ return false;
+ }
+
+ boolean isCameraRunningAndWindowingModeEligible(@NonNull ActivityRecord activity,
+ boolean mustBeFullscreen) {
// Checking windowing mode on activity level because we don't want to
// apply treatment in case of activity embedding.
return (!mustBeFullscreen || !activity.inMultiWindowMode())
@@ -299,7 +315,8 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp
private boolean isTreatmentEnabledForActivity(@Nullable ActivityRecord activity,
boolean mustBeFullscreen) {
- return activity != null && isCameraActive(activity, mustBeFullscreen)
+ return activity != null
+ && isCameraRunningAndWindowingModeEligible(activity, mustBeFullscreen)
&& activity.getRequestedConfigurationOrientation() != ORIENTATION_UNDEFINED
// "locked" and "nosensor" values are often used by camera apps that can't
// handle dynamic changes so we shouldn't force rotate them.
@@ -428,6 +445,7 @@ final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraComp
private boolean shouldOverrideMinAspectRatio(@NonNull ActivityRecord activityRecord) {
return activityRecord.mAppCompatController.getAppCompatCameraOverrides()
.isOverrideMinAspectRatioForCameraEnabled()
- && isCameraActive(activityRecord, /* mustBeFullscreen= */ true);
+ && isCameraRunningAndWindowingModeEligible(activityRecord,
+ /* mustBeFullscreen= */ true);
}
}
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index ddbfd70ea4c4..d7dc4597c508 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -222,7 +222,8 @@ final class InputMonitor {
UserHandle clientUser) {
final InputConsumerImpl existingConsumer = getInputConsumer(name);
if (existingConsumer != null && existingConsumer.mClientUser.equals(clientUser)) {
- throw new IllegalStateException("Existing input consumer found with name: " + name
+ destroyInputConsumer(existingConsumer.mToken);
+ Slog.w(TAG_WM, "Replacing existing input consumer found with name: " + name
+ ", display: " + mDisplayId + ", user: " + clientUser);
}
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 4f8332a49750..9e6468020fc0 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -162,6 +162,12 @@ class InsetsSourceProvider {
return mSource;
}
+ @VisibleForTesting
+ @NonNull
+ Rect getSourceFrame() {
+ return mSourceFrame;
+ }
+
/**
* @return Whether the current flag configuration allows to control this source.
*/
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
index db0374e52b1a..e4fd523d5ce7 100644
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -815,8 +815,7 @@ class ScreenRotationAnimation {
if (mDisplayContent.getRotationAnimation() == ScreenRotationAnimation.this) {
// It also invokes kill().
mDisplayContent.setRotationAnimation(null);
- mDisplayContent.mAppCompatCameraPolicy
- .onScreenRotationAnimationFinished();
+ mDisplayContent.mAppCompatCameraPolicy.onScreenRotationAnimationFinished();
} else {
kill();
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 86bb75ab3f8c..edbc32827c1a 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -66,6 +66,7 @@ import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_LOCKTASK;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN;
import static com.android.server.wm.ActivityRecord.State.PAUSED;
import static com.android.server.wm.ActivityRecord.State.PAUSING;
import static com.android.server.wm.ActivityRecord.State.RESUMED;
@@ -1474,7 +1475,7 @@ class Task extends TaskFragment {
// The starting window should keep covering its task when a pure TaskFragment is added
// because its bounds may not fill the task.
final ActivityRecord top = getTopMostActivity();
- if (top != null) {
+ if (top != null && !top.hasFixedRotationTransform()) {
top.associateStartingWindowWithTaskIfNeeded();
}
}
@@ -4706,8 +4707,13 @@ class Task extends TaskFragment {
// If the moveToFront is a part of finishing transition, then make sure
// the z-order of tasks are up-to-date.
if (topActivity.mTransitionController.inFinishingTransition(topActivity)) {
- Transition.assignLayers(taskDisplayArea,
- taskDisplayArea.getPendingTransaction());
+ final SurfaceControl.Transaction tx =
+ taskDisplayArea.getPendingTransaction();
+ Transition.assignLayers(taskDisplayArea, tx);
+ final SurfaceControl leash = topActivity.getFixedRotationLeash();
+ if (leash != null) {
+ tx.setLayer(leash, topActivity.getLastLayer());
+ }
}
}
}
@@ -6177,6 +6183,8 @@ class Task extends TaskFragment {
void maybeApplyLastRecentsAnimationTransaction() {
if (mLastRecentsAnimationTransaction != null) {
+ ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS_MIN,
+ "Applying last recents animation transaction.");
final SurfaceControl.Transaction tx = getPendingTransaction();
if (mLastRecentsAnimationOverlay != null) {
tx.reparent(mLastRecentsAnimationOverlay, mSurfaceControl);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index b8f47cce6005..942634704ff5 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -9007,7 +9007,9 @@ public class WindowManagerService extends IWindowManager.Stub
final boolean isInputTargetNotFocused =
mFocusedInputTarget != t && mFocusedInputTarget != null;
- if (!isInputTargetNotFocused) {
+ final boolean isTouchOnFocusedDisplay = mFocusedInputTarget != null
+ && t.getDisplayId() == mFocusedInputTarget.getDisplayId();
+ if (!(isInputTargetNotFocused && isTouchOnFocusedDisplay)) {
return false;
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index 06d8c370b914..6d7396f1f477 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -1038,6 +1038,25 @@ public class WindowManagerShellCommand extends ShellCommand {
return 0;
}
+ private int runSetCameraCompatAspectRatio(PrintWriter pw) throws RemoteException {
+ final float aspectRatio;
+ try {
+ String arg = getNextArgRequired();
+ aspectRatio = Float.parseFloat(arg);
+ } catch (NumberFormatException e) {
+ getErrPrintWriter().println("Error: bad aspect ratio format " + e);
+ return -1;
+ } catch (IllegalArgumentException e) {
+ getErrPrintWriter().println(
+ "Error: aspect ratio should be provided as an argument " + e);
+ return -1;
+ }
+ synchronized (mInternal.mGlobalLock) {
+ mAppCompatConfiguration.setCameraCompatAspectRatio(aspectRatio);
+ }
+ return 0;
+ }
+
private int runSetLetterboxStyle(PrintWriter pw) throws RemoteException {
if (peekNextArg() == null) {
getErrPrintWriter().println("Error: No arguments provided.");
@@ -1129,6 +1148,9 @@ public class WindowManagerShellCommand extends ShellCommand {
runSetBooleanFlag(pw,
mAppCompatConfiguration::setCameraCompatRefreshCycleThroughStopEnabled);
break;
+ case "--cameraCompatAspectRatio":
+ runSetCameraCompatAspectRatio(pw);
+ break;
default:
getErrPrintWriter().println(
"Error: Unrecognized letterbox style option: " + arg);
@@ -1220,6 +1242,9 @@ public class WindowManagerShellCommand extends ShellCommand {
mAppCompatConfiguration
.resetCameraCompatRefreshCycleThroughStopEnabled();
break;
+ case "cameraCompatAspectRatio":
+ mAppCompatConfiguration.resetCameraCompatAspectRatio();
+ break;
default:
getErrPrintWriter().println(
"Error: Unrecognized letterbox style option: " + arg);
@@ -1330,6 +1355,7 @@ public class WindowManagerShellCommand extends ShellCommand {
mAppCompatConfiguration.resetUserAppAspectRatioFullscreenEnabled();
mAppCompatConfiguration.resetCameraCompatRefreshEnabled();
mAppCompatConfiguration.resetCameraCompatRefreshCycleThroughStopEnabled();
+ mAppCompatConfiguration.resetCameraCompatAspectRatio();
}
}
@@ -1619,6 +1645,11 @@ public class WindowManagerShellCommand extends ShellCommand {
pw.println(" Whether activity \"refresh\" in camera compatibility treatment should");
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(" freeform camera compat mode. If aspectRatio <= "
+ + AppCompatConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO);
+ pw.println(" it will be ignored.");
pw.println(" reset-letterbox-style [aspectRatio|cornerRadius|backgroundType");
pw.println(" |backgroundColor|wallpaperBlurRadius|wallpaperDarkScrimAlpha");
pw.println(" |horizontalPositionMultiplier|verticalPositionMultiplier");
@@ -1627,7 +1658,8 @@ public class WindowManagerShellCommand extends ShellCommand {
pw.println(" |isTranslucentLetterboxingEnabled|isUserAppAspectRatioSettingsEnabled");
pw.println(" |persistentPositionMultiplierForHorizontalReachability");
pw.println(" |persistentPositionMultiplierForVerticalReachability");
- pw.println(" |defaultPositionMultiplierForVerticalReachability]");
+ pw.println(" |defaultPositionMultiplierForVerticalReachability");
+ pw.println(" |cameraCompatAspectRatio]");
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/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 476443aa2050..f35f2b30c5d4 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -799,7 +799,12 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
}
} finally {
if (deferTransitionReady) {
- chain.mTransition.continueTransitionReady();
+ if (chain.mTransition.isCollecting()) {
+ chain.mTransition.continueTransitionReady();
+ } else {
+ Slog.wtf(TAG, "Too late, transition : " + chain.mTransition.getSyncId()
+ + " state: " + chain.mTransition.getState() + " is not collecting");
+ }
}
mService.mTaskSupervisor.setDeferRootVisibilityUpdate(false /* deferUpdate */);
if (deferResume) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 4c4b4f65edf5..ab8384601f8f 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1378,6 +1378,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
// should be updated after the new given insets are sent to window manager.
return;
}
+ if (!mRelayoutCalled) {
+ // The window was not laid out yet. The source frame should be updated after the window
+ // is laid out.
+ return;
+ }
final SparseArray<InsetsSourceProvider> providers = getInsetsSourceProviders();
for (int i = providers.size() - 1; i >= 0; i--) {
providers.valueAt(i).updateSourceFrame(winFrame);
@@ -1969,6 +1974,13 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
boolean isReadyForDisplay() {
final boolean parentAndClientVisible = !isParentWindowHidden()
&& mViewVisibility == View.VISIBLE;
+ // TODO(b/338426357): Remove this once the last target using legacy transitions is moved to
+ // shell transitions
+ if (!mTransitionController.isShellTransitionsEnabled()) {
+ return mHasSurface && isVisibleByPolicy() && !mDestroying
+ && ((parentAndClientVisible && mToken.isVisible())
+ || isAnimating(TRANSITION | PARENTS));
+ }
return mHasSurface && isVisibleByPolicy() && !mDestroying && mToken.isVisible()
&& (parentAndClientVisible || isAnimating(TRANSITION | PARENTS));
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 5cf260adece6..ce6f1ecc9463 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -205,6 +205,7 @@ import com.android.server.os.SchedulingPolicyService;
import com.android.server.pdb.PersistentDataBlockService;
import com.android.server.people.PeopleService;
import com.android.server.permission.access.AccessCheckingService;
+import com.android.server.pinner.PinnerService;
import com.android.server.pm.ApexManager;
import com.android.server.pm.ApexSystemServiceInfo;
import com.android.server.pm.BackgroundInstallControlService;
diff --git a/services/profcollect/src/com/android/server/profcollect/Utils.java b/services/profcollect/src/com/android/server/profcollect/Utils.java
index 850880256cfa..b4e254442a19 100644
--- a/services/profcollect/src/com/android/server/profcollect/Utils.java
+++ b/services/profcollect/src/com/android/server/profcollect/Utils.java
@@ -19,6 +19,7 @@ package com.android.server.profcollect;
import static com.android.server.profcollect.ProfcollectForwardingService.LOG_TAG;
import android.os.RemoteException;
+import android.os.ServiceSpecificException;
import android.provider.DeviceConfig;
import android.util.Log;
@@ -42,7 +43,7 @@ public final class Utils {
BackgroundThread.get().getThreadHandler().post(() -> {
try {
mIProfcollect.trace_system(eventName);
- } catch (RemoteException e) {
+ } catch (RemoteException | ServiceSpecificException e) {
Log.e(LOG_TAG, "Failed to initiate trace: " + e.getMessage());
}
});
@@ -56,7 +57,7 @@ public final class Utils {
BackgroundThread.get().getThreadHandler().postDelayed(() -> {
try {
mIProfcollect.trace_system(eventName);
- } catch (RemoteException e) {
+ } catch (RemoteException | ServiceSpecificException e) {
Log.e(LOG_TAG, "Failed to initiate trace: " + e.getMessage());
}
}, delayMs);
@@ -73,10 +74,10 @@ public final class Utils {
mIProfcollect.trace_process(eventName,
processName,
durationMs);
- } catch (RemoteException e) {
+ } catch (RemoteException | ServiceSpecificException e) {
Log.e(LOG_TAG, "Failed to initiate trace: " + e.getMessage());
}
});
return true;
}
-} \ No newline at end of file
+}
diff --git a/services/tests/RemoteProvisioningServiceTests/Android.bp b/services/tests/RemoteProvisioningServiceTests/Android.bp
index 19c913620760..3a73c3954d52 100644
--- a/services/tests/RemoteProvisioningServiceTests/Android.bp
+++ b/services/tests/RemoteProvisioningServiceTests/Android.bp
@@ -31,7 +31,6 @@ android_test {
"service-rkp.impl",
"services.core",
"truth",
- "truth-java8-extension",
],
test_suites: [
"device-tests",
diff --git a/services/tests/RemoteProvisioningServiceTests/src/com/android/server/security/rkp/RemoteProvisioningShellCommandTest.java b/services/tests/RemoteProvisioningServiceTests/src/com/android/server/security/rkp/RemoteProvisioningShellCommandTest.java
index 007c0db1b731..a1616c676dbd 100644
--- a/services/tests/RemoteProvisioningServiceTests/src/com/android/server/security/rkp/RemoteProvisioningShellCommandTest.java
+++ b/services/tests/RemoteProvisioningServiceTests/src/com/android/server/security/rkp/RemoteProvisioningShellCommandTest.java
@@ -17,7 +17,6 @@
package com.android.server.security.rkp;
import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth8.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
diff --git a/services/tests/appfunctions/src/android/app/appfunctions/GenericDocumentWrapperTest.kt b/services/tests/appfunctions/src/android/app/appfunctions/GenericDocumentWrapperTest.kt
new file mode 100644
index 000000000000..413eb314c41d
--- /dev/null
+++ b/services/tests/appfunctions/src/android/app/appfunctions/GenericDocumentWrapperTest.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.app.appfunctions
+
+import android.app.appsearch.GenericDocument
+import android.os.Parcel
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+
+@RunWith(JUnit4::class)
+class GenericDocumentWrapperTest {
+
+ @Test
+ fun parcelUnparcel() {
+ val doc =
+ GenericDocument.Builder<GenericDocument.Builder<*>>("", "", "")
+ .setPropertyLong("test", 42)
+ .build()
+ val wrapper = GenericDocumentWrapper(doc)
+
+ val recovered = parcelUnparcel(wrapper)
+
+ assertThat(recovered.value.getPropertyLong("test")).isEqualTo(42)
+ }
+
+ @Test
+ fun parcelUnparcel_afterGetValue() {
+ val doc =
+ GenericDocument.Builder<GenericDocument.Builder<*>>("", "", "")
+ .setPropertyLong("test", 42)
+ .build()
+ val wrapper = GenericDocumentWrapper(doc)
+ assertThat(wrapper.value.getPropertyLong("test")).isEqualTo(42)
+
+ val recovered = parcelUnparcel(wrapper)
+
+ assertThat(recovered.value.getPropertyLong("test")).isEqualTo(42)
+ }
+
+
+ @Test
+ fun getValue() {
+ val doc =
+ GenericDocument.Builder<GenericDocument.Builder<*>>("", "", "")
+ .setPropertyLong("test", 42)
+ .build()
+ val wrapper = GenericDocumentWrapper(doc)
+
+ assertThat(wrapper.value.getPropertyLong("test")).isEqualTo(42)
+ }
+
+ private fun parcelUnparcel(obj: GenericDocumentWrapper): GenericDocumentWrapper {
+ val parcel = Parcel.obtain()
+ try {
+ obj.writeToParcel(parcel, 0)
+ parcel.setDataPosition(0)
+ return GenericDocumentWrapper.CREATOR.createFromParcel(parcel)
+ } finally {
+ parcel.recycle()
+ }
+ }
+} \ No newline at end of file
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayEventDeliveryTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayEventDeliveryTest.java
index 90f62577b261..d00e2c677930 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayEventDeliveryTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayEventDeliveryTest.java
@@ -25,6 +25,7 @@ import static android.util.DisplayMetrics.DENSITY_MEDIUM;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
import android.app.ActivityManager;
import android.app.Instrumentation;
@@ -38,6 +39,9 @@ import android.os.Looper;
import android.os.Message;
import android.os.Messenger;
import android.platform.test.annotations.AppModeSdkSandbox;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.util.Log;
import android.util.SparseArray;
@@ -46,15 +50,19 @@ import androidx.annotation.NonNull;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.compatibility.common.util.SystemUtil;
+import com.android.compatibility.common.util.TestUtils;
+import com.android.server.am.Flags;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
+import java.io.IOException;
import java.util.Arrays;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
@@ -68,6 +76,10 @@ import java.util.concurrent.TimeUnit;
public class DisplayEventDeliveryTest {
private static final String TAG = "DisplayEventDeliveryTest";
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
private static final String NAME = TAG;
private static final int WIDTH = 720;
private static final int HEIGHT = 480;
@@ -149,7 +161,6 @@ public class DisplayEventDeliveryTest {
mExpectations.offer(event);
}
-
/**
* Assert that there isn't any unexpected display event from the test activity
*/
@@ -189,19 +200,9 @@ public class DisplayEventDeliveryTest {
@Parameter(0)
public int mDisplayCount;
- /**
- * True if running the test activity in cached mode
- * False if running it in non-cached mode
- */
- @Parameter(1)
- public boolean mCached;
-
- @Parameters(name = "#{index}: {0} {1}")
+ @Parameters(name = "#{index}: {0}")
public static Iterable<? extends Object> data() {
- return Arrays.asList(new Object[][]{
- {1, false}, {2, false}, {3, false}, {10, false},
- {1, true}, {2, true}, {3, true}, {10, true}
- });
+ return Arrays.asList(new Object[][]{ {1}, {2}, {3}, {10} });
}
private class TestHandler extends Handler {
@@ -289,20 +290,51 @@ public class DisplayEventDeliveryTest {
}
/**
- * Create virtual displays, change their configurations and release them
- * mDisplays: the amount of virtual displays to be created
- * mCached: true to run the test activity in cached mode; false in non-cached mode
+ * Return true if the freezer is enabled on this platform.
*/
- @Test
- public void testDisplayEvents() {
- Log.d(TAG, "Start test testDisplayEvents " + mDisplayCount + " " + mCached);
+ private boolean isAppFreezerEnabled() {
+ try {
+ return mActivityManager.getService().isAppFreezerEnabled();
+ } catch (Exception e) {
+ Log.e(TAG, "isAppFreezerEnabled() failed: " + e);
+ return false;
+ }
+ }
+
+ private void waitForProcessFreeze(int pid, long timeoutMs) {
+ // TODO: Add a listener to monitor freezer state changes.
+ SystemUtil.runWithShellPermissionIdentity(() -> {
+ TestUtils.waitUntil("Timed out waiting for test process to be frozen; pid=" + pid,
+ (int) TimeUnit.MILLISECONDS.toSeconds(timeoutMs),
+ () -> mActivityManager.isProcessFrozen(pid));
+ });
+ }
+
+ private void waitForProcessUnfreeze(int pid, long timeoutMs) {
+ // TODO: Add a listener to monitor freezer state changes.
+ SystemUtil.runWithShellPermissionIdentity(() -> {
+ TestUtils.waitUntil("Timed out waiting for test process to be frozen; pid=" + pid,
+ (int) TimeUnit.MILLISECONDS.toSeconds(timeoutMs),
+ () -> !mActivityManager.isProcessFrozen(pid));
+ });
+ }
+
+ /**
+ * Create virtual displays, change their configurations and release them. The number of
+ * displays is set by the {@link #mDisplays} variable.
+ */
+ private void testDisplayEventsInternal(boolean cached, boolean frozen) {
+ Log.d(TAG, "Start test testDisplayEvents " + mDisplayCount + " " + cached + " " + frozen);
// Launch DisplayEventActivity and start listening to display events
- launchTestActivity();
+ int pid = launchTestActivity();
- if (mCached) {
- // The test activity in cached mode won't receive the pending display events
+ // The test activity in cached or frozen mode won't receive the pending display events.
+ if (cached) {
makeTestActivityCached();
}
+ if (frozen) {
+ makeTestActivityFrozen(pid);
+ }
// Create new virtual displays
for (int i = 0; i < mDisplayCount; i++) {
@@ -315,8 +347,8 @@ public class DisplayEventDeliveryTest {
}
for (int i = 0; i < mDisplayCount; i++) {
- if (mCached) {
- // DISPLAY_ADDED should be deferred for cached process
+ if (cached || frozen) {
+ // DISPLAY_ADDED should be deferred for a cached or frozen process.
displayBundleAt(i).assertNoDisplayEvents();
} else {
// DISPLAY_ADDED should arrive immediately for non-cached process
@@ -331,8 +363,8 @@ public class DisplayEventDeliveryTest {
}
for (int i = 0; i < mDisplayCount; i++) {
- if (mCached) {
- // DISPLAY_CHANGED should be deferred for cached process
+ if (cached || frozen) {
+ // DISPLAY_CHANGED should be deferred for cached or frozen process.
displayBundleAt(i).assertNoDisplayEvents();
} else {
// DISPLAY_CHANGED should arrive immediately for non-cached process
@@ -340,10 +372,16 @@ public class DisplayEventDeliveryTest {
}
}
- if (mCached) {
- // The test activity becomes non-cached and should receive the pending display events
+ // Unfreeze the test activity, if it was frozen.
+ if (frozen) {
+ makeTestActivityUnfrozen(pid);
+ }
+
+ if (cached || frozen) {
+ // Always ensure the test activity is not cached.
bringTestActivityTop();
+ // The test activity becomes non-cached and should receive the pending display events
for (int i = 0; i < mDisplayCount; i++) {
// The pending DISPLAY_ADDED & DISPLAY_CHANGED should arrive now
displayBundleAt(i).waitDisplayEvent(DISPLAY_ADDED);
@@ -363,9 +401,48 @@ public class DisplayEventDeliveryTest {
}
/**
- * Launch the test activity that would listen to display events
+ * Create virtual displays, change their configurations and release them.
+ */
+ @Test
+ public void testDisplayEvents() {
+ testDisplayEventsInternal(false, false);
+ }
+
+ /**
+ * Create virtual displays, change their configurations and release them. The display app is
+ * moved to cached and the test verifies that no events are delivered to the cached app.
*/
- private void launchTestActivity() {
+ @Test
+ public void testDisplayEventsCached() {
+ testDisplayEventsInternal(true, false);
+ }
+
+ /**
+ * Create virtual displays, change their configurations and release them. The display app is
+ * frozen and the test verifies that no events are delivered to the frozen app.
+ */
+ @RequiresFlagsEnabled(Flags.FLAG_DEFER_DISPLAY_EVENTS_WHEN_FROZEN)
+ @Test
+ public void testDisplayEventsFrozen() {
+ assumeTrue(isAppFreezerEnabled());
+ testDisplayEventsInternal(false, true);
+ }
+
+ /**
+ * Create virtual displays, change their configurations and release them. The display app is
+ * cached and frozen and the test verifies that no events are delivered to the app.
+ */
+ @RequiresFlagsEnabled(Flags.FLAG_DEFER_DISPLAY_EVENTS_WHEN_FROZEN)
+ @Test
+ public void testDisplayEventsCachedFrozen() {
+ assumeTrue(isAppFreezerEnabled());
+ testDisplayEventsInternal(true, true);
+ }
+
+ /**
+ * Launch the test activity that would listen to display events. Return its process ID.
+ */
+ private int launchTestActivity() {
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.setClassName(TEST_PACKAGE, TEST_ACTIVITY);
intent.putExtra(TEST_MESSENGER, mMessenger);
@@ -377,6 +454,18 @@ public class DisplayEventDeliveryTest {
},
android.Manifest.permission.START_ACTIVITIES_FROM_SDK_SANDBOX);
waitLatch(mLatchActivityLaunch);
+
+ try {
+ String cmd = "pidof " + TEST_PACKAGE;
+ String result = SystemUtil.runShellCommand(mInstrumentation, cmd);
+ return Integer.parseInt(result.trim());
+ } catch (IOException e) {
+ fail("failed to get pid of test package");
+ return 0;
+ } catch (NumberFormatException e) {
+ fail("failed to parse pid " + e);
+ return 0;
+ }
}
/**
@@ -415,6 +504,45 @@ public class DisplayEventDeliveryTest {
waitLatch(mLatchActivityCached);
}
+ // Sleep, ignoring interrupts.
+ private void pause(int s) {
+ try { Thread.sleep(s * 1000); } catch (Exception e) { }
+ }
+
+ /**
+ * Freeze the test activity.
+ */
+ private void makeTestActivityFrozen(int pid) {
+ // The delay here is meant to allow pending binder transactions to drain. A process
+ // cannot be frozen if it has pending binder transactions, and attempting to freeze such a
+ // process more than a few times will result in the system killing the process.
+ pause(5);
+ try {
+ String cmd = "am freeze --sticky ";
+ SystemUtil.runShellCommand(mInstrumentation, cmd + TEST_PACKAGE);
+ } catch (IOException e) {
+ fail(e.toString());
+ }
+ // Wait for the freeze to complete in the kernel and for the frozen process
+ // notification to settle out.
+ waitForProcessFreeze(pid, 5 * 1000);
+ }
+
+ /**
+ * Freeze the test activity.
+ */
+ private void makeTestActivityUnfrozen(int pid) {
+ try {
+ String cmd = "am unfreeze --sticky ";
+ SystemUtil.runShellCommand(mInstrumentation, cmd + TEST_PACKAGE);
+ } catch (IOException e) {
+ fail(e.toString());
+ }
+ // Wait for the freeze to complete in the kernel and for the frozen process
+ // notification to settle out.
+ waitForProcessUnfreeze(pid, 5 * 1000);
+ }
+
/**
* Create a virtual display
*
diff --git a/services/tests/powerstatstests/res/xml/irq_device_map_3.xml b/services/tests/powerstatstests/res/xml/irq_device_map_3.xml
index fd55428c48df..c3df0785bd9b 100644
--- a/services/tests/powerstatstests/res/xml/irq_device_map_3.xml
+++ b/services/tests/powerstatstests/res/xml/irq_device_map_3.xml
@@ -32,4 +32,7 @@
<device name="test.sensor.device">
<subsystem>Sensor</subsystem>
</device>
+ <device name="test.bluetooth.device">
+ <subsystem>Bluetooth</subsystem>
+ </device>
</irq-device-map> \ No newline at end of file
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/wakeups/CpuWakeupStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/wakeups/CpuWakeupStatsTest.java
index 0dc836ba0400..fe4d971face5 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/wakeups/CpuWakeupStatsTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/wakeups/CpuWakeupStatsTest.java
@@ -17,6 +17,7 @@
package com.android.server.power.stats.wakeups;
import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_ALARM;
+import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_BLUETOOTH;
import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_CELLULAR_DATA;
import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_SENSOR;
import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER;
@@ -52,6 +53,7 @@ public class CpuWakeupStatsTest {
private static final String KERNEL_REASON_SOUND_TRIGGER_IRQ = "129 test.sound_trigger.device";
private static final String KERNEL_REASON_SENSOR_IRQ = "15 test.sensor.device";
private static final String KERNEL_REASON_CELLULAR_DATA_IRQ = "18 test.cellular_data.device";
+ private static final String KERNEL_REASON_BLUETOOTH_IRQ = "19 test.bluetooth.device";
private static final String KERNEL_REASON_UNKNOWN_IRQ = "140 test.unknown.device";
private static final String KERNEL_REASON_UNKNOWN_FORMAT = "free-form-reason test.alarm.device";
private static final String KERNEL_REASON_ALARM_ABNORMAL = "-1 test.alarm.device";
@@ -62,12 +64,14 @@ public class CpuWakeupStatsTest {
private static final int TEST_UID_3 = 92261423;
private static final int TEST_UID_4 = 56926423;
private static final int TEST_UID_5 = 76421423;
+ private static final int TEST_UID_6 = 62345353;
private static final int TEST_PROC_STATE_1 = 72331;
private static final int TEST_PROC_STATE_2 = 792351;
private static final int TEST_PROC_STATE_3 = 138831;
private static final int TEST_PROC_STATE_4 = 23231;
private static final int TEST_PROC_STATE_5 = 42;
+ private static final int TEST_PROC_STATE_6 = 129942;
private static final Context sContext = InstrumentationRegistry.getTargetContext();
private final Handler mHandler = Mockito.mock(Handler.class);
@@ -79,6 +83,7 @@ public class CpuWakeupStatsTest {
obj.mUidProcStates.put(TEST_UID_3, TEST_PROC_STATE_3);
obj.mUidProcStates.put(TEST_UID_4, TEST_PROC_STATE_4);
obj.mUidProcStates.put(TEST_UID_5, TEST_PROC_STATE_5);
+ obj.mUidProcStates.put(TEST_UID_6, TEST_PROC_STATE_6);
}
@Test
@@ -118,6 +123,7 @@ public class CpuWakeupStatsTest {
CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER,
CPU_WAKEUP_SUBSYSTEM_SENSOR,
CPU_WAKEUP_SUBSYSTEM_CELLULAR_DATA,
+ CPU_WAKEUP_SUBSYSTEM_BLUETOOTH,
};
final String[] kernelReasons = new String[] {
@@ -126,10 +132,11 @@ public class CpuWakeupStatsTest {
KERNEL_REASON_SOUND_TRIGGER_IRQ,
KERNEL_REASON_SENSOR_IRQ,
KERNEL_REASON_CELLULAR_DATA_IRQ,
+ KERNEL_REASON_BLUETOOTH_IRQ,
};
final int[] uids = new int[] {
- TEST_UID_2, TEST_UID_3, TEST_UID_4, TEST_UID_1, TEST_UID_5
+ TEST_UID_2, TEST_UID_3, TEST_UID_4, TEST_UID_1, TEST_UID_5, TEST_UID_6
};
final int[] procStates = new int[] {
@@ -137,7 +144,8 @@ public class CpuWakeupStatsTest {
TEST_PROC_STATE_3,
TEST_PROC_STATE_4,
TEST_PROC_STATE_1,
- TEST_PROC_STATE_5
+ TEST_PROC_STATE_5,
+ TEST_PROC_STATE_6
};
final int total = subsystems.length;
@@ -285,6 +293,40 @@ public class CpuWakeupStatsTest {
}
@Test
+ public void bluetoothIrqAttributionSolo() {
+ final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3, mHandler);
+ final long wakeupTime = 1236121;
+
+ populateDefaultProcStates(obj);
+
+ obj.noteWakeupTimeAndReason(wakeupTime, 1, KERNEL_REASON_BLUETOOTH_IRQ);
+
+ // Outside the window, so should be ignored.
+ obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_BLUETOOTH,
+ wakeupTime - obj.mConfig.WAKEUP_MATCHING_WINDOW_MS - 1, TEST_UID_1);
+ obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_BLUETOOTH,
+ wakeupTime + obj.mConfig.WAKEUP_MATCHING_WINDOW_MS + 1, TEST_UID_2);
+ // Should be attributed
+ obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_BLUETOOTH, wakeupTime + 5, TEST_UID_3,
+ TEST_UID_5);
+
+ final SparseArray<SparseIntArray> attribution = obj.mWakeupAttribution.get(wakeupTime);
+ assertThat(attribution).isNotNull();
+ assertThat(attribution.size()).isEqualTo(1);
+ assertThat(attribution.contains(CPU_WAKEUP_SUBSYSTEM_BLUETOOTH)).isTrue();
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_BLUETOOTH).indexOfKey(
+ TEST_UID_1)).isLessThan(0);
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_BLUETOOTH).indexOfKey(
+ TEST_UID_2)).isLessThan(0);
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_BLUETOOTH).get(TEST_UID_3)).isEqualTo(
+ TEST_PROC_STATE_3);
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_BLUETOOTH).indexOfKey(
+ TEST_UID_4)).isLessThan(0);
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_BLUETOOTH).get(TEST_UID_5)).isEqualTo(
+ TEST_PROC_STATE_5);
+ }
+
+ @Test
public void alarmAndWifiIrqAttribution() {
final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3, mHandler);
final long wakeupTime = 92123210;
@@ -400,6 +442,47 @@ public class CpuWakeupStatsTest {
}
@Test
+ public void unknownAndBluetoothAttribution() {
+ final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3, mHandler);
+ final long wakeupTime = 92123520;
+
+ populateDefaultProcStates(obj);
+
+ obj.noteWakeupTimeAndReason(wakeupTime, 24,
+ KERNEL_REASON_UNKNOWN_IRQ + ":" + KERNEL_REASON_BLUETOOTH_IRQ);
+
+ // Bluetooth activity
+ // Outside the window, so should be ignored.
+ obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_BLUETOOTH,
+ wakeupTime - obj.mConfig.WAKEUP_MATCHING_WINDOW_MS - 1, TEST_UID_4);
+ // Should be attributed
+ obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_BLUETOOTH, wakeupTime + 2, TEST_UID_1);
+ obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_BLUETOOTH, wakeupTime - 1, TEST_UID_3,
+ TEST_UID_5);
+
+ // Unrelated, should be ignored.
+ obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, wakeupTime + 5, TEST_UID_3);
+
+ final SparseArray<SparseIntArray> attribution = obj.mWakeupAttribution.get(wakeupTime);
+ assertThat(attribution).isNotNull();
+ assertThat(attribution.size()).isEqualTo(2);
+ assertThat(attribution.contains(CPU_WAKEUP_SUBSYSTEM_BLUETOOTH)).isTrue();
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_BLUETOOTH).get(TEST_UID_1)).isEqualTo(
+ TEST_PROC_STATE_1);
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_BLUETOOTH)
+ .indexOfKey(TEST_UID_2)).isLessThan(0);
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_BLUETOOTH).get(TEST_UID_3)).isEqualTo(
+ TEST_PROC_STATE_3);
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_BLUETOOTH)
+ .indexOfKey(TEST_UID_4)).isLessThan(0);
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_BLUETOOTH).get(TEST_UID_5)).isEqualTo(
+ TEST_PROC_STATE_5);
+ assertThat(attribution.contains(CPU_WAKEUP_SUBSYSTEM_UNKNOWN)).isTrue();
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_UNKNOWN)).isNull();
+ assertThat(attribution.contains(CPU_WAKEUP_SUBSYSTEM_ALARM)).isFalse();
+ }
+
+ @Test
public void unknownFormatWakeupIgnored() {
final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3, mHandler);
final long wakeupTime = 72123210;
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index ac1b7c6876f7..cbe6700f4d41 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -52,6 +52,7 @@ android_test {
"services.credentials",
"services.devicepolicy",
"services.flags",
+ "com.android.server.flags.services-aconfig-java",
"services.net",
"services.people",
"services.supervision",
@@ -81,6 +82,7 @@ android_test {
// TODO: remove once Android migrates to JUnit 4.12,
// which provides assertThrows
"testng",
+ "flag-junit",
"junit",
"junit-params",
"ActivityContext",
diff --git a/services/tests/servicestests/src/com/android/server/PinnerServiceTest.java b/services/tests/servicestests/src/com/android/server/PinnerServiceTest.java
index ec78bcea7539..c18faef2c028 100644
--- a/services/tests/servicestests/src/com/android/server/PinnerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/PinnerServiceTest.java
@@ -31,6 +31,9 @@ import android.content.pm.ResolveInfo;
import android.os.Binder;
import android.os.Handler;
import android.os.Looper;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.DeviceConfig;
import android.provider.DeviceConfigInterface;
import android.testing.TestableContext;
@@ -43,6 +46,9 @@ import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.server.flags.Flags;
+import com.android.server.pinner.PinnedFile;
+import com.android.server.pinner.PinnerService;
import com.android.server.testutils.FakeDeviceConfigInterface;
import com.android.server.wm.ActivityTaskManagerInternal;
@@ -73,15 +79,18 @@ public class PinnerServiceTest {
private static final long WAIT_FOR_PINNER_TIMEOUT = TimeUnit.SECONDS.toMillis(2);
+ private static final int MEMORY_PERCENTAGE_FOR_QUOTA = 10;
+
@Rule
public TestableContext mContext =
new TestableContext(InstrumentationRegistry.getContext(), null);
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
private final ArraySet<String> mUpdatedPackages = new ArraySet<>();
private ResolveInfo mHomePackageResolveInfo;
private FakeDeviceConfigInterface mFakeDeviceConfigInterface;
private PinnerService.Injector mInjector;
-
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -114,6 +123,8 @@ public class PinnerServiceTest {
resources.addOverride(com.android.internal.R.bool.config_pinnerCameraApp, false);
resources.addOverride(com.android.internal.R.integer.config_pinnerHomePinBytes, 0);
resources.addOverride(com.android.internal.R.bool.config_pinnerAssistantApp, false);
+ resources.addOverride(com.android.internal.R.integer.config_pinnerMaxPinnedMemoryPercentage,
+ MEMORY_PERCENTAGE_FOR_QUOTA);
mFakeDeviceConfigInterface = new FakeDeviceConfigInterface();
setDeviceConfigPinnedAnonSize(0);
@@ -138,10 +149,9 @@ public class PinnerServiceTest {
}
@Override
- protected PinnerService.PinnedFile pinFileInternal(String fileToPin,
- int maxBytesToPin, boolean attemptPinIntrospection) {
- return new PinnerService.PinnedFile(-1,
- maxBytesToPin, fileToPin, maxBytesToPin);
+ protected PinnedFile pinFileInternal(PinnerService service, String fileToPin,
+ long maxBytesToPin, boolean attemptPinIntrospection) {
+ return new PinnedFile(-1, maxBytesToPin, fileToPin, maxBytesToPin);
}
};
}
@@ -167,6 +177,12 @@ public class PinnerServiceTest {
unpinAnonRegionMethod.invoke(pinnerService);
}
+ private long getGlobalPinQuota(PinnerService service) throws Exception {
+ Method getQuotaMethod = PinnerService.class.getDeclaredMethod("getAvailableGlobalQuota");
+ getQuotaMethod.setAccessible(true);
+ return (long) getQuotaMethod.invoke(service);
+ }
+
private void waitForPinnerService(PinnerService pinnerService)
throws NoSuchFieldException, IllegalAccessException {
// There's no notification/callback when pinning finished
@@ -315,15 +331,121 @@ public class PinnerServiceTest {
PinnerService pinnerService = new PinnerService(mContext, mInjector);
pinnerService.onStart();
- pinnerService.pinFile("test_file", 4096, null, "my_group");
+ pinnerService.pinFile("test_file", 4096, null, "my_group", false);
- assertThat(getPinnedSize(pinnerService)).isGreaterThan(0);
- assertThat(getTotalPinnedFiles(pinnerService)).isGreaterThan(0);
+ assertThat(getPinnedSize(pinnerService)).isEqualTo(4096);
+ assertThat(getTotalPinnedFiles(pinnerService)).isEqualTo(1);
+
+ unpinAll(pinnerService);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_PIN_GLOBAL_QUOTA)
+ public void testPinAllQuota() throws Exception {
+ PinnerService pinnerService = new PinnerService(mContext, mInjector);
+ pinnerService.onStart();
+
+ long quota = getGlobalPinQuota(pinnerService);
+
+ pinnerService.pinFile("test_file", Long.MAX_VALUE, null, "my_group", false);
+
+ assertThat(getPinnedSize(pinnerService)).isEqualTo(quota);
unpinAll(pinnerService);
}
@Test
+ @EnableFlags(Flags.FLAG_PIN_GLOBAL_QUOTA)
+ public void testGlobalPinQuotaAsDevicePercentage() throws Exception {
+ PinnerService pinnerService = new PinnerService(mContext, mInjector);
+ pinnerService.onStart();
+ long origQuota = getGlobalPinQuota(pinnerService);
+
+ long totalMem = android.os.Process.getTotalMemory();
+
+ // Verify that pin quota is the set percentage of device total memory
+ assertThat(origQuota).isEqualTo((totalMem * MEMORY_PERCENTAGE_FOR_QUOTA) / 100);
+
+ pinnerService.pinFile("test_file", 4096, null, "my_group", false);
+ assertThat(getGlobalPinQuota(pinnerService)).isEqualTo(origQuota - 4096);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_PIN_GLOBAL_QUOTA)
+ public void testGlobalPinWhenNoQuota() throws Exception {
+ TestableResources resources = mContext.getOrCreateTestableResources();
+ resources.addOverride(
+ com.android.internal.R.integer.config_pinnerMaxPinnedMemoryPercentage, 0);
+
+ PinnerService pinnerService = new PinnerService(mContext, mInjector);
+ pinnerService.onStart();
+
+ // Verify that pin quota is zero
+ assertThat(getGlobalPinQuota(pinnerService)).isEqualTo(0);
+
+ pinnerService.pinFile("test_file", 4096, null, "my_group", false);
+ assertThat(getTotalPinnedFiles(pinnerService)).isEqualTo(0);
+ }
+
+ /**
+ * This test is temporary, it should be cleaned up when removing the pin_global_quota bugfix
+ * flag.
+ */
+ @Test
+ @DisableFlags(Flags.FLAG_PIN_GLOBAL_QUOTA)
+ public void testGlobalQuotaDisabled() throws Exception {
+ TestableResources resources = mContext.getOrCreateTestableResources();
+ resources.addOverride(
+ com.android.internal.R.integer.config_pinnerMaxPinnedMemoryPercentage, 0);
+
+ PinnerService pinnerService = new PinnerService(mContext, mInjector);
+ pinnerService.onStart();
+
+ // The quota parameter exists but it should have no effect on pinning
+ long quota = getGlobalPinQuota(pinnerService);
+
+ pinnerService.pinFile("test_file", quota + 1, null, "my_group", false);
+
+ // Verify that we can pin past the quota as it is disabled
+ assertThat(getPinnedSize(pinnerService)).isEqualTo(quota + 1);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_PIN_GLOBAL_QUOTA)
+ public void testUnpinReleasesQuota() throws Exception {
+ PinnerService pinnerService = new PinnerService(mContext, mInjector);
+ pinnerService.onStart();
+ long origQuota = getGlobalPinQuota(pinnerService);
+
+ // Verify that pin quota exists and is non zero.
+ assertThat(getGlobalPinQuota(pinnerService)).isGreaterThan(0);
+
+ pinnerService.pinFile("test_file", origQuota, null, "my_group", false);
+
+ // Make sure all the quota was consumed
+ assertThat(getPinnedSize(pinnerService)).isEqualTo(origQuota);
+
+ // Unpin the file and verify that the quota has been released.
+ pinnerService.unpinFile("test_file");
+ assertThat(getPinnedSize(pinnerService)).isEqualTo(0);
+ assertThat(getGlobalPinQuota(pinnerService)).isEqualTo(origQuota);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_PIN_GLOBAL_QUOTA)
+ public void testGlobalPinQuotaNegative() throws Exception {
+ TestableResources resources = mContext.getOrCreateTestableResources();
+ resources.addOverride(
+ com.android.internal.R.integer.config_pinnerMaxPinnedMemoryPercentage, -10);
+
+ PinnerService pinnerService = new PinnerService(mContext, mInjector);
+ pinnerService.onStart();
+
+ // Verify that pin quota is zero
+ assertThat(getGlobalPinQuota(pinnerService)).isEqualTo(0);
+ }
+
+ @Test
public void testPinAnonRegion() throws Exception {
setDeviceConfigPinnedAnonSize(32768);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
index 6e6d5a870031..8dfd54fe38bc 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
@@ -174,8 +174,8 @@ public class AbstractAccessibilityServiceConnectionTest {
@Mock private AccessibilityTrace mMockA11yTrace;
@Mock private WindowManagerInternal mMockWindowManagerInternal;
@Mock private SystemActionPerformer mMockSystemActionPerformer;
- @Mock private IBinder mMockService;
- @Mock private IAccessibilityServiceClient mMockServiceInterface;
+ @Mock private IBinder mMockClientBinder;
+ @Mock private IAccessibilityServiceClient mMockClient;
@Mock private KeyEventDispatcher mMockKeyEventDispatcher;
@Mock private IAccessibilityInteractionConnection mMockIA11yInteractionConnection;
@Mock private IAccessibilityInteractionConnectionCallback mMockCallback;
@@ -247,9 +247,9 @@ public class AbstractAccessibilityServiceConnectionTest {
mSpyServiceInfo, SERVICE_ID, mHandler, new Object(), mMockSecurityPolicy,
mMockSystemSupport, mMockA11yTrace, mMockWindowManagerInternal,
mMockSystemActionPerformer, mMockA11yWindowManager);
- // Assume that the service is connected
- mServiceConnection.mService = mMockService;
- mServiceConnection.mServiceInterface = mMockServiceInterface;
+ // Assume that the client is connected
+ mServiceConnection.mClientBinder = mMockClientBinder;
+ mServiceConnection.mClient = mMockClient;
// Update security policy for this service
when(mMockSecurityPolicy.checkAccessibilityAccess(mServiceConnection)).thenReturn(true);
@@ -273,7 +273,7 @@ public class AbstractAccessibilityServiceConnectionTest {
final KeyEvent mockKeyEvent = mock(KeyEvent.class);
mServiceConnection.onKeyEvent(mockKeyEvent, sequenceNumber);
- verify(mMockServiceInterface).onKeyEvent(mockKeyEvent, sequenceNumber);
+ verify(mMockClient).onKeyEvent(mockKeyEvent, sequenceNumber);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
index b9ce8ad0b018..0c92abce7254 100644
--- a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
@@ -1163,6 +1163,16 @@ public class AccountManagerServiceTest extends AndroidTestCase {
verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture());
Bundle result = mBundleCaptor.getValue();
+ Bundle sessionBundle = result.getBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE);
+ assertNotNull(sessionBundle);
+ // Assert that session bundle is decrypted and hence data is visible.
+ assertEquals(AccountManagerServiceTestFixtures.SESSION_DATA_VALUE_1,
+ sessionBundle.getString(AccountManagerServiceTestFixtures.SESSION_DATA_NAME_1));
+ // Assert finishSessionAsUser added calling uid and pid into the sessionBundle
+ assertTrue(sessionBundle.containsKey(AccountManager.KEY_CALLER_UID));
+ assertTrue(sessionBundle.containsKey(AccountManager.KEY_CALLER_PID));
+ assertEquals(sessionBundle.getString(
+ AccountManager.KEY_ANDROID_PACKAGE_NAME), "APCT.package");
// Verify response data
assertNull(result.getString(AccountManager.KEY_AUTHTOKEN, null));
@@ -2111,6 +2121,12 @@ public class AccountManagerServiceTest extends AndroidTestCase {
result.getString(AccountManager.KEY_ACCOUNT_NAME));
assertEquals(AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1,
result.getString(AccountManager.KEY_ACCOUNT_TYPE));
+
+ Bundle optionBundle = result.getParcelable(
+ AccountManagerServiceTestFixtures.KEY_OPTIONS_BUNDLE);
+ // Assert addAccountAsUser added calling uid and pid into the option bundle
+ assertTrue(optionBundle.containsKey(AccountManager.KEY_CALLER_UID));
+ assertTrue(optionBundle.containsKey(AccountManager.KEY_CALLER_PID));
}
@SmallTest
@@ -3441,52 +3457,6 @@ public class AccountManagerServiceTest extends AndroidTestCase {
+ (readTotalTime.doubleValue() / readerCount / loopSize));
}
- @SmallTest
- public void testSanitizeBundle_expectedFields() throws Exception {
- Bundle bundle = new Bundle();
- bundle.putString(AccountManager.KEY_ACCOUNT_NAME, "name");
- bundle.putString(AccountManager.KEY_ACCOUNT_TYPE, "type");
- bundle.putString(AccountManager.KEY_AUTHTOKEN, "token");
- bundle.putString(AccountManager.KEY_AUTH_TOKEN_LABEL, "label");
- bundle.putString(AccountManager.KEY_ERROR_MESSAGE, "error message");
- bundle.putString(AccountManager.KEY_PASSWORD, "password");
- bundle.putString(AccountManager.KEY_ACCOUNT_STATUS_TOKEN, "status");
-
- bundle.putLong(AbstractAccountAuthenticator.KEY_CUSTOM_TOKEN_EXPIRY, 123L);
- bundle.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true);
- bundle.putInt(AccountManager.KEY_ERROR_CODE, 456);
-
- Bundle sanitizedBundle = AccountManagerService.sanitizeBundle(bundle);
- assertEquals(sanitizedBundle.getString(AccountManager.KEY_ACCOUNT_NAME), "name");
- assertEquals(sanitizedBundle.getString(AccountManager.KEY_ACCOUNT_TYPE), "type");
- assertEquals(sanitizedBundle.getString(AccountManager.KEY_AUTHTOKEN), "token");
- assertEquals(sanitizedBundle.getString(AccountManager.KEY_AUTH_TOKEN_LABEL), "label");
- assertEquals(sanitizedBundle.getString(AccountManager.KEY_ERROR_MESSAGE), "error message");
- assertEquals(sanitizedBundle.getString(AccountManager.KEY_PASSWORD), "password");
- assertEquals(sanitizedBundle.getString(AccountManager.KEY_ACCOUNT_STATUS_TOKEN), "status");
-
- assertEquals(sanitizedBundle.getLong(
- AbstractAccountAuthenticator.KEY_CUSTOM_TOKEN_EXPIRY, 0), 123L);
- assertEquals(sanitizedBundle.getBoolean(AccountManager.KEY_BOOLEAN_RESULT, false), true);
- assertEquals(sanitizedBundle.getInt(AccountManager.KEY_ERROR_CODE, 0), 456);
- }
-
- @SmallTest
- public void testSanitizeBundle_filtersUnexpectedFields() throws Exception {
- Bundle bundle = new Bundle();
- bundle.putString(AccountManager.KEY_ACCOUNT_NAME, "name");
- bundle.putString("unknown_key", "value");
- Bundle sessionBundle = new Bundle();
- bundle.putBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE, sessionBundle);
-
- Bundle sanitizedBundle = AccountManagerService.sanitizeBundle(bundle);
-
- assertEquals(sanitizedBundle.getString(AccountManager.KEY_ACCOUNT_NAME), "name");
- assertFalse(sanitizedBundle.containsKey("unknown_key"));
- // It is a valid response from Authenticator which will be accessed using original Bundle
- assertFalse(sanitizedBundle.containsKey(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE));
- }
-
private void waitForCyclicBarrier(CyclicBarrier cyclicBarrier) {
try {
cyclicBarrier.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java b/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java
index 14cb22d7698e..efc2d974a7cc 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java
@@ -16,12 +16,20 @@
package com.android.server.biometrics;
+import static android.Manifest.permission.SET_BIOMETRIC_DIALOG_ADVANCED;
import static android.hardware.biometrics.BiometricManager.Authenticators;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doThrow;
+
+import android.content.Context;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricManager;
@@ -36,8 +44,12 @@ import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import androidx.test.filters.SmallTest;
+import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
@Presubmit
@SmallTest
@@ -45,6 +57,17 @@ public class UtilsTest {
@Rule
public final CheckFlagsRule mCheckFlagsRule =
DeviceFlagsValueProvider.createCheckFlagsRule();
+ @Rule
+ public MockitoRule mockitorule = MockitoJUnit.rule();
+
+ @Mock
+ private Context mContext;
+
+ @Before
+ public void setUp() {
+ doThrow(SecurityException.class).when(mContext).enforceCallingOrSelfPermission(
+ eq(SET_BIOMETRIC_DIALOG_ADVANCED), any());
+ }
@Test
public void testCombineAuthenticatorBundles_withKeyDeviceCredential_andKeyAuthenticators() {
@@ -162,28 +185,39 @@ public class UtilsTest {
@Test
public void testIsValidAuthenticatorConfig() {
- assertTrue(Utils.isValidAuthenticatorConfig(Authenticators.EMPTY_SET));
+ assertTrue(Utils.isValidAuthenticatorConfig(mContext, Authenticators.EMPTY_SET));
- assertTrue(Utils.isValidAuthenticatorConfig(Authenticators.BIOMETRIC_STRONG));
+ assertTrue(Utils.isValidAuthenticatorConfig(mContext, Authenticators.BIOMETRIC_STRONG));
- assertTrue(Utils.isValidAuthenticatorConfig(Authenticators.BIOMETRIC_WEAK));
+ assertTrue(Utils.isValidAuthenticatorConfig(mContext, Authenticators.BIOMETRIC_WEAK));
- assertTrue(Utils.isValidAuthenticatorConfig(Authenticators.DEVICE_CREDENTIAL));
+ assertTrue(Utils.isValidAuthenticatorConfig(mContext, Authenticators.DEVICE_CREDENTIAL));
- assertTrue(Utils.isValidAuthenticatorConfig(Authenticators.DEVICE_CREDENTIAL
+ assertTrue(Utils.isValidAuthenticatorConfig(mContext, Authenticators.DEVICE_CREDENTIAL
| Authenticators.BIOMETRIC_STRONG));
- assertTrue(Utils.isValidAuthenticatorConfig(Authenticators.DEVICE_CREDENTIAL
+ assertTrue(Utils.isValidAuthenticatorConfig(mContext, Authenticators.DEVICE_CREDENTIAL
| Authenticators.BIOMETRIC_WEAK));
- assertFalse(Utils.isValidAuthenticatorConfig(Authenticators.BIOMETRIC_CONVENIENCE));
+ assertFalse(Utils.isValidAuthenticatorConfig(
+ mContext, Authenticators.BIOMETRIC_CONVENIENCE));
- assertFalse(Utils.isValidAuthenticatorConfig(Authenticators.BIOMETRIC_CONVENIENCE
+ assertFalse(Utils.isValidAuthenticatorConfig(mContext, Authenticators.BIOMETRIC_CONVENIENCE
| Authenticators.DEVICE_CREDENTIAL));
- assertFalse(Utils.isValidAuthenticatorConfig(Authenticators.BIOMETRIC_MAX_STRENGTH));
+ assertFalse(Utils.isValidAuthenticatorConfig(
+ mContext, Authenticators.BIOMETRIC_MAX_STRENGTH));
+
+ assertFalse(Utils.isValidAuthenticatorConfig(
+ mContext, Authenticators.BIOMETRIC_MIN_STRENGTH));
+
+ assertThrows(SecurityException.class, () -> Utils.isValidAuthenticatorConfig(
+ mContext, Authenticators.MANDATORY_BIOMETRICS));
+
+ doNothing().when(mContext).enforceCallingOrSelfPermission(
+ eq(SET_BIOMETRIC_DIALOG_ADVANCED), any());
- assertFalse(Utils.isValidAuthenticatorConfig(Authenticators.BIOMETRIC_MIN_STRENGTH));
+ assertTrue(Utils.isValidAuthenticatorConfig(mContext, Authenticators.MANDATORY_BIOMETRICS));
// The rest of the bits are not allowed to integrate with the public APIs
for (int i = 8; i < 32; i++) {
@@ -192,7 +226,7 @@ public class UtilsTest {
|| authenticator == Authenticators.MANDATORY_BIOMETRICS) {
continue;
}
- assertFalse(Utils.isValidAuthenticatorConfig(1 << i));
+ assertFalse(Utils.isValidAuthenticatorConfig(mContext, 1 << i));
}
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
index d071c159d6f5..ae781dcb834a 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
@@ -60,6 +60,7 @@ import android.os.RemoteException;
import android.os.ServiceSpecificException;
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;
@@ -130,6 +131,7 @@ public class RebootEscrowManagerTests {
private SecretKey mAesKey;
private MockInjector mMockInjector;
private Handler mHandler;
+ private Network mNetwork;
public interface MockableRebootEscrowInjected {
int getBootCount();
@@ -342,6 +344,7 @@ public class RebootEscrowManagerTests {
when(mCallbacks.isUserSecure(NONSECURE_SECONDARY_USER_ID)).thenReturn(false);
when(mCallbacks.isUserSecure(SECURE_SECONDARY_USER_ID)).thenReturn(true);
mInjected = mock(MockableRebootEscrowInjected.class);
+ mNetwork = mock(Network.class);
mMockInjector =
new MockInjector(
mContext,
@@ -351,6 +354,10 @@ public class RebootEscrowManagerTests {
mKeyStoreManager,
mStorage,
mInjected);
+ mMockInjector.mNetworkConsumer =
+ (callback) -> {
+ callback.onAvailable(mNetwork);
+ };
HandlerThread thread = new HandlerThread("RebootEscrowManagerTest");
thread.start();
mHandler = new Handler(thread.getLooper());
@@ -367,6 +374,10 @@ public class RebootEscrowManagerTests {
mKeyStoreManager,
mStorage,
mInjected);
+ mMockInjector.mNetworkConsumer =
+ (callback) -> {
+ callback.onAvailable(mNetwork);
+ };
mService = new RebootEscrowManager(mMockInjector, mCallbacks, mStorage, mHandler);
}
@@ -621,7 +632,7 @@ public class RebootEscrowManagerTests {
// pretend reboot happens here
when(mInjected.getBootCount()).thenReturn(1);
- mService.loadRebootEscrowDataIfAvailable(null);
+ mService.loadRebootEscrowDataIfAvailable(mHandler);
verify(mServiceConnection, never()).unwrap(any(), anyLong());
verify(mCallbacks, never()).onRebootEscrowRestored(anyByte(), any(), anyInt());
}
@@ -678,7 +689,7 @@ public class RebootEscrowManagerTests {
when(mServiceConnection.unwrap(any(), anyLong()))
.thenAnswer(invocation -> invocation.getArgument(0));
- mService.loadRebootEscrowDataIfAvailable(null);
+ mService.loadRebootEscrowDataIfAvailable(mHandler);
verify(mServiceConnection).unwrap(any(), anyLong());
verify(mCallbacks).onRebootEscrowRestored(anyByte(), any(), eq(PRIMARY_USER_ID));
@@ -734,7 +745,7 @@ public class RebootEscrowManagerTests {
when(mServiceConnection.unwrap(any(), anyLong()))
.thenAnswer(invocation -> invocation.getArgument(0));
- mService.loadRebootEscrowDataIfAvailable(null);
+ mService.loadRebootEscrowDataIfAvailable(mHandler);
verify(mServiceConnection).unwrap(any(), anyLong());
verify(mCallbacks).onRebootEscrowRestored(anyByte(), any(), eq(PRIMARY_USER_ID));
@@ -783,7 +794,7 @@ public class RebootEscrowManagerTests {
when(mServiceConnection.unwrap(any(), anyLong()))
.thenAnswer(invocation -> invocation.getArgument(0));
- mService.loadRebootEscrowDataIfAvailable(null);
+ mService.loadRebootEscrowDataIfAvailable(mHandler);
verify(mServiceConnection).unwrap(any(), anyLong());
assertTrue(metricsSuccessCaptor.getValue());
verify(mKeyStoreManager).clearKeyStoreEncryptionKey();
@@ -827,7 +838,7 @@ public class RebootEscrowManagerTests {
anyInt());
when(mServiceConnection.unwrap(any(), anyLong())).thenThrow(RemoteException.class);
- mService.loadRebootEscrowDataIfAvailable(null);
+ mService.loadRebootEscrowDataIfAvailable(mHandler);
verify(mServiceConnection).unwrap(any(), anyLong());
assertFalse(metricsSuccessCaptor.getValue());
assertEquals(
@@ -836,6 +847,7 @@ public class RebootEscrowManagerTests {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_WAIT_FOR_INTERNET_ROR)
public void loadRebootEscrowDataIfAvailable_ServerBasedIoError_RetryFailure() throws Exception {
setServerBasedRebootEscrowProvider();
@@ -930,114 +942,6 @@ public class RebootEscrowManagerTests {
@Test
@RequiresFlagsEnabled(Flags.FLAG_WAIT_FOR_INTERNET_ROR)
- public void loadRebootEscrowDataIfAvailable_serverBasedWaitForInternet_success()
- throws Exception {
- setServerBasedRebootEscrowProvider();
-
- when(mInjected.getBootCount()).thenReturn(0);
- RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
- mService.setRebootEscrowListener(mockListener);
- mService.prepareRebootEscrow();
-
- clearInvocations(mServiceConnection);
- callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
- verify(mockListener).onPreparedForReboot(eq(true));
- verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
-
- // Use x -> x for both wrap & unwrap functions.
- when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong()))
- .thenAnswer(invocation -> invocation.getArgument(0));
- assertEquals(ARM_REBOOT_ERROR_NONE, mService.armRebootEscrowIfNeeded());
- verify(mServiceConnection).wrapBlob(any(), anyLong(), anyLong());
- assertTrue(mStorage.hasRebootEscrowServerBlob());
-
- // pretend reboot happens here
- when(mInjected.getBootCount()).thenReturn(1);
- ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class);
- doNothing()
- .when(mInjected)
- .reportMetric(
- metricsSuccessCaptor.capture(),
- eq(0) /* error code */,
- eq(2) /* Server based */,
- eq(1) /* attempt count */,
- anyInt(),
- eq(0) /* vbmeta status */,
- anyInt());
-
- // load escrow data
- when(mServiceConnection.unwrap(any(), anyLong()))
- .thenAnswer(invocation -> invocation.getArgument(0));
- Network mockNetwork = mock(Network.class);
- mMockInjector.mNetworkConsumer =
- (callback) -> {
- callback.onAvailable(mockNetwork);
- };
-
- mService.loadRebootEscrowDataIfAvailable(mHandler);
- verify(mServiceConnection).unwrap(any(), anyLong());
- assertTrue(metricsSuccessCaptor.getValue());
- verify(mKeyStoreManager).clearKeyStoreEncryptionKey();
- assertNull(mMockInjector.mNetworkCallback);
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_WAIT_FOR_INTERNET_ROR)
- public void loadRebootEscrowDataIfAvailable_serverBasedWaitForInternetRemoteException_Failure()
- throws Exception {
- setServerBasedRebootEscrowProvider();
-
- when(mInjected.getBootCount()).thenReturn(0);
- RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
- mService.setRebootEscrowListener(mockListener);
- mService.prepareRebootEscrow();
-
- clearInvocations(mServiceConnection);
- callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
- verify(mockListener).onPreparedForReboot(eq(true));
- verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
-
- // Use x -> x for both wrap & unwrap functions.
- when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong()))
- .thenAnswer(invocation -> invocation.getArgument(0));
- assertEquals(ARM_REBOOT_ERROR_NONE, mService.armRebootEscrowIfNeeded());
- verify(mServiceConnection).wrapBlob(any(), anyLong(), anyLong());
- assertTrue(mStorage.hasRebootEscrowServerBlob());
-
- // pretend reboot happens here
- when(mInjected.getBootCount()).thenReturn(1);
- ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class);
- ArgumentCaptor<Integer> metricsErrorCodeCaptor = ArgumentCaptor.forClass(Integer.class);
- doNothing()
- .when(mInjected)
- .reportMetric(
- metricsSuccessCaptor.capture(),
- metricsErrorCodeCaptor.capture(),
- eq(2) /* Server based */,
- eq(1) /* attempt count */,
- anyInt(),
- eq(0) /* vbmeta status */,
- anyInt());
-
- // load escrow data
- when(mServiceConnection.unwrap(any(), anyLong())).thenThrow(RemoteException.class);
- Network mockNetwork = mock(Network.class);
- mMockInjector.mNetworkConsumer =
- (callback) -> {
- callback.onAvailable(mockNetwork);
- };
-
- mService.loadRebootEscrowDataIfAvailable(mHandler);
- verify(mServiceConnection).unwrap(any(), anyLong());
- assertFalse(metricsSuccessCaptor.getValue());
- assertEquals(
- Integer.valueOf(RebootEscrowManager.ERROR_LOAD_ESCROW_KEY),
- metricsErrorCodeCaptor.getValue());
- assertNull(mMockInjector.mNetworkCallback);
- }
-
- @Test
- @RequiresFlagsEnabled(Flags.FLAG_WAIT_FOR_INTERNET_ROR)
public void loadRebootEscrowDataIfAvailable_waitForInternet_networkUnavailable()
throws Exception {
setServerBasedRebootEscrowProvider();
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
index abc9ce3fdc36..ee63d5d32ff1 100644
--- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java
@@ -38,6 +38,7 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
@@ -91,6 +92,7 @@ import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Answers;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
@@ -174,8 +176,8 @@ public class MediaProjectionManagerServiceTest {
private PackageManager mPackageManager;
@Mock
private KeyguardManager mKeyguardManager;
- @Mock
- AppOpsManager mAppOpsManager;
+
+ private AppOpsManager mAppOpsManager;
@Mock
private IMediaProjectionWatcherCallback mWatcherCallback;
@Mock
@@ -193,6 +195,7 @@ public class MediaProjectionManagerServiceTest {
LocalServices.removeServiceForTest(WindowManagerInternal.class);
LocalServices.addService(WindowManagerInternal.class, mWindowManagerInternal);
+ mAppOpsManager = mockAppOpsManager();
mContext.addMockSystemService(AppOpsManager.class, mAppOpsManager);
mContext.addMockSystemService(KeyguardManager.class, mKeyguardManager);
mContext.setMockPackageManager(mPackageManager);
@@ -206,6 +209,17 @@ public class MediaProjectionManagerServiceTest {
mService = new MediaProjectionManagerService(mContext);
}
+ private static AppOpsManager mockAppOpsManager() {
+ return mock(AppOpsManager.class, invocationOnMock -> {
+ if (invocationOnMock.getMethod().getName().startsWith("noteOp")) {
+ // Mockito will return 0 for non-stubbed method which corresponds to MODE_ALLOWED
+ // and is not what we want.
+ return AppOpsManager.MODE_IGNORED;
+ }
+ return Answers.RETURNS_DEFAULTS.answer(invocationOnMock);
+ });
+ }
+
@After
public void tearDown() {
LocalServices.removeServiceForTest(ActivityManagerInternal.class);
@@ -305,8 +319,10 @@ public class MediaProjectionManagerServiceTest {
public void testCreateProjection_keyguardLocked_AppOpMediaProjection()
throws NameNotFoundException {
MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions();
- doReturn(true).when(mAppOpsManager).isOperationActive(eq(AppOpsManager.OP_PROJECT_MEDIA),
- eq(projection.uid), eq(projection.packageName));
+ doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOpsManager)
+ .noteOpNoThrow(eq(AppOpsManager.OP_PROJECT_MEDIA),
+ eq(projection.uid), eq(projection.packageName), nullable(String.class),
+ nullable(String.class));
doReturn(true).when(mKeyguardManager).isKeyguardLocked();
doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager).checkPermission(
@@ -1159,7 +1175,7 @@ public class MediaProjectionManagerServiceTest {
doReturn(mAppInfo).when(mPackageManager).getApplicationInfoAsUser(anyString(),
any(ApplicationInfoFlags.class), any(UserHandle.class));
return service.createProjectionInternal(UID, PACKAGE_NAME,
- TYPE_MIRRORING, /* isPermanentGrant= */ true, UserHandle.CURRENT);
+ TYPE_MIRRORING, /* isPermanentGrant= */ false, UserHandle.CURRENT);
}
// Set up preconditions for starting a projection, with no foreground service requirements.
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
index 62e5b9a3dccc..45cd5719cd86 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
@@ -31,6 +31,12 @@ import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.media.AudioAttributes.USAGE_NOTIFICATION;
import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
+import static com.android.server.notification.NotificationAttentionHelper.MUTE_REASON_COOLDOWN;
+import static com.android.server.notification.NotificationAttentionHelper.MUTE_REASON_FLAG_SILENT;
+import static com.android.server.notification.NotificationAttentionHelper.MUTE_REASON_GROUP_ALERT;
+import static com.android.server.notification.NotificationAttentionHelper.MUTE_REASON_NOT_MUTED;
+import static com.android.server.notification.NotificationAttentionHelper.MUTE_REASON_OTHER_INSISTENT_PLAYING;
+
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
@@ -106,6 +112,7 @@ import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.Notificat
import com.android.internal.config.sysui.TestableFlagResolver;
import com.android.internal.logging.InstanceIdSequence;
import com.android.internal.logging.InstanceIdSequenceFake;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.util.IntPair;
import com.android.server.UiServiceTestCase;
import com.android.server.lights.LightsManager;
@@ -1276,7 +1283,8 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
verifyNeverBeep();
assertFalse(r.isInterruptive());
assertEquals(-1, r.getLastAudiblyAlertedMs());
- assertTrue(mAttentionHelper.shouldMuteNotificationLocked(r, DEFAULT_SIGNALS));
+ assertThat(mAttentionHelper.shouldMuteNotificationLocked(r, DEFAULT_SIGNALS,
+ true)).isEqualTo(MUTE_REASON_FLAG_SILENT);
}
@Test
@@ -1295,7 +1303,8 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
verifyNeverBeep();
assertFalse(r.isInterruptive());
assertEquals(-1, r.getLastAudiblyAlertedMs());
- assertTrue(mAttentionHelper.shouldMuteNotificationLocked(r, DEFAULT_SIGNALS));
+ assertThat(mAttentionHelper.shouldMuteNotificationLocked(r, DEFAULT_SIGNALS,
+ true)).isEqualTo(MUTE_REASON_GROUP_ALERT);
}
@Test
@@ -1861,7 +1870,9 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
verifyBeepLooped();
NotificationRecord interrupter = getBeepyOtherNotification();
- assertTrue(mAttentionHelper.shouldMuteNotificationLocked(interrupter, DEFAULT_SIGNALS));
+ assertThat(
+ mAttentionHelper.shouldMuteNotificationLocked(interrupter, DEFAULT_SIGNALS,
+ true)).isEqualTo(MUTE_REASON_OTHER_INSISTENT_PLAYING);
mAttentionHelper.buzzBeepBlinkLocked(interrupter, DEFAULT_SIGNALS);
verifyBeep(1);
@@ -1879,16 +1890,16 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
ringtoneChannel.enableVibration(true);
NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, true);
mService.addNotification(ringtoneNotification);
- assertFalse(mAttentionHelper.shouldMuteNotificationLocked(ringtoneNotification,
- DEFAULT_SIGNALS));
+ assertThat(mAttentionHelper.shouldMuteNotificationLocked(ringtoneNotification,
+ DEFAULT_SIGNALS, true)).isEqualTo(MUTE_REASON_NOT_MUTED);
mAttentionHelper.buzzBeepBlinkLocked(ringtoneNotification, DEFAULT_SIGNALS);
verifyBeepLooped();
verifyDelayedVibrateLooped();
Mockito.reset(mVibrator);
Mockito.reset(mRingtonePlayer);
- assertFalse(mAttentionHelper.shouldMuteNotificationLocked(ringtoneNotification,
- DEFAULT_SIGNALS));
+ assertThat(mAttentionHelper.shouldMuteNotificationLocked(ringtoneNotification,
+ DEFAULT_SIGNALS, true)).isEqualTo(MUTE_REASON_NOT_MUTED);
mAttentionHelper.buzzBeepBlinkLocked(ringtoneNotification, DEFAULT_SIGNALS);
// beep wasn't reset
@@ -1907,8 +1918,8 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
ringtoneChannel.enableVibration(true);
NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, true);
mService.addNotification(ringtoneNotification);
- assertFalse(mAttentionHelper.shouldMuteNotificationLocked(ringtoneNotification,
- DEFAULT_SIGNALS));
+ assertThat(mAttentionHelper.shouldMuteNotificationLocked(ringtoneNotification,
+ DEFAULT_SIGNALS, true)).isEqualTo(MUTE_REASON_NOT_MUTED);
mAttentionHelper.buzzBeepBlinkLocked(ringtoneNotification, DEFAULT_SIGNALS);
verifyBeepLooped();
verifyDelayedVibrateLooped();
@@ -1930,8 +1941,8 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
ringtoneChannel.enableVibration(true);
NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, true);
mService.addNotification(ringtoneNotification);
- assertFalse(mAttentionHelper.shouldMuteNotificationLocked(ringtoneNotification,
- DEFAULT_SIGNALS));
+ assertThat(mAttentionHelper.shouldMuteNotificationLocked(ringtoneNotification,
+ DEFAULT_SIGNALS, true)).isEqualTo(MUTE_REASON_NOT_MUTED);
mAttentionHelper.buzzBeepBlinkLocked(ringtoneNotification, DEFAULT_SIGNALS);
verifyBeepLooped();
verifyNeverVibrate();
@@ -1951,14 +1962,15 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build());
ringtoneChannel.enableVibration(true);
NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, true);
- assertFalse(mAttentionHelper.shouldMuteNotificationLocked(ringtoneNotification,
- DEFAULT_SIGNALS));
+ assertThat(mAttentionHelper.shouldMuteNotificationLocked(ringtoneNotification,
+ DEFAULT_SIGNALS, true)).isEqualTo(MUTE_REASON_NOT_MUTED);
mAttentionHelper.buzzBeepBlinkLocked(ringtoneNotification, DEFAULT_SIGNALS);
verifyVibrateLooped();
NotificationRecord interrupter = getBuzzyOtherNotification();
- assertTrue(mAttentionHelper.shouldMuteNotificationLocked(interrupter, DEFAULT_SIGNALS));
+ assertThat(mAttentionHelper.shouldMuteNotificationLocked(interrupter,
+ DEFAULT_SIGNALS, true)).isEqualTo(MUTE_REASON_OTHER_INSISTENT_PLAYING);
mAttentionHelper.buzzBeepBlinkLocked(interrupter, DEFAULT_SIGNALS);
verifyVibrate(1);
@@ -2260,10 +2272,13 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
// 2nd update should beep at 0% volume
Mockito.reset(mRingtonePlayer);
- mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
- verifyBeepVolume(0.0f);
+ int buzzBeepBlink = mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+ verifyNeverBeep();
+ assertThat(buzzBeepBlink).isEqualTo(MetricsEvent.ALERT_MUTED | MUTE_REASON_COOLDOWN);
+ assertThat(mAttentionHelper.shouldMuteNotificationLocked(r, DEFAULT_SIGNALS, true))
+ .isEqualTo(MUTE_REASON_COOLDOWN);
- verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt());
+ verify(mAccessibilityService, times(2)).sendAccessibilityEvent(any(), anyInt());
assertEquals(-1, r.getLastAudiblyAlertedMs());
}
@@ -2305,8 +2320,9 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
// 2nd update should beep at 0% volume
Mockito.reset(mRingtonePlayer);
- mAttentionHelper.buzzBeepBlinkLocked(r3, DEFAULT_SIGNALS);
- verifyBeepVolume(0.0f);
+ int buzzBeepBlink = mAttentionHelper.buzzBeepBlinkLocked(r3, DEFAULT_SIGNALS);
+ verifyNeverBeep();
+ assertThat(buzzBeepBlink).isEqualTo(MetricsEvent.ALERT_MUTED | MUTE_REASON_COOLDOWN);
verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt());
assertEquals(-1, r3.getLastAudiblyAlertedMs());
@@ -2381,9 +2397,10 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
false, null, Notification.GROUP_ALERT_ALL, false, mUser, "anotherPkg");
// update should beep at 0% volume
- mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS);
+ int buzzBeepBlink = mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS);
assertEquals(-1, r2.getLastAudiblyAlertedMs());
- verifyBeepVolume(0.0f);
+ verifyNeverBeep();
+ assertThat(buzzBeepBlink).isEqualTo(MetricsEvent.ALERT_MUTED | MUTE_REASON_COOLDOWN);
// Use different package for next notifications
NotificationRecord r3 = getNotificationRecord(mId, false /* insistent */, false /* once */,
@@ -2392,8 +2409,9 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
// 2nd update should beep at 0% volume
Mockito.reset(mRingtonePlayer);
- mAttentionHelper.buzzBeepBlinkLocked(r3, DEFAULT_SIGNALS);
- verifyBeepVolume(0.0f);
+ buzzBeepBlink = mAttentionHelper.buzzBeepBlinkLocked(r3, DEFAULT_SIGNALS);
+ verifyNeverBeep();
+ assertThat(buzzBeepBlink).isEqualTo(MetricsEvent.ALERT_MUTED | MUTE_REASON_COOLDOWN);
verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt());
assertEquals(-1, r3.getLastAudiblyAlertedMs());
@@ -2493,8 +2511,9 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
// Regular notification: should beep at 0% volume
NotificationRecord r = getBeepyNotification();
- mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
- verifyBeepVolume(0.0f);
+ int buzzBeepBlink = mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+ verifyNeverBeep();
+ assertThat(buzzBeepBlink).isEqualTo(MetricsEvent.ALERT_MUTED | MUTE_REASON_COOLDOWN);
assertEquals(-1, r.getLastAudiblyAlertedMs());
Mockito.reset(mRingtonePlayer);
@@ -2525,8 +2544,9 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
// 2nd update should beep at 0% volume
Mockito.reset(mRingtonePlayer);
- mAttentionHelper.buzzBeepBlinkLocked(r3, DEFAULT_SIGNALS);
- verifyBeepVolume(0.0f);
+ buzzBeepBlink = mAttentionHelper.buzzBeepBlinkLocked(r3, DEFAULT_SIGNALS);
+ verifyNeverBeep();
+ assertThat(buzzBeepBlink).isEqualTo(MetricsEvent.ALERT_MUTED | MUTE_REASON_COOLDOWN);
// Set important conversation
mChannel.setImportantConversation(true);
@@ -2751,9 +2771,10 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
Mockito.reset(mRingtonePlayer);
// next update at 0% volume
- mAttentionHelper.buzzBeepBlinkLocked(summary, DEFAULT_SIGNALS);
+ int buzzBeepBlink = mAttentionHelper.buzzBeepBlinkLocked(summary, DEFAULT_SIGNALS);
assertEquals(-1, summary.getLastAudiblyAlertedMs());
- verifyBeepVolume(0.0f);
+ verifyNeverBeep();
+ assertThat(buzzBeepBlink).isEqualTo(MetricsEvent.ALERT_MUTED | MUTE_REASON_COOLDOWN);
verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt());
}
@@ -2823,9 +2844,10 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
// 2nd update should beep at 0% volume
Mockito.reset(mRingtonePlayer);
- mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS);
+ int buzzBeepBlink = mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS);
assertEquals(-1, r2.getLastAudiblyAlertedMs());
- verifyBeepVolume(0.0f);
+ verifyNeverBeep();
+ assertThat(buzzBeepBlink).isEqualTo(MetricsEvent.ALERT_MUTED | MUTE_REASON_COOLDOWN);
// Use different package for next notifications
NotificationRecord r3 = getNotificationRecord(mId, false /* insistent */, false /* once */,
@@ -2891,6 +2913,94 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
}
@Test
+ public void testBeepVolume_politeNotif_groupAlertSummary() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+ mSetFlagsRule.disableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
+ TestableFlagResolver flagResolver = new TestableFlagResolver();
+ flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
+ flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
+ // NOTIFICATION_COOLDOWN_ALL setting is enabled
+ Settings.System.putInt(getContext().getContentResolver(),
+ Settings.System.NOTIFICATION_COOLDOWN_ALL, 1);
+ initAttentionHelper(flagResolver);
+
+ // child should beep at 0% volume
+ NotificationRecord child = getBeepyNotificationRecord("a", GROUP_ALERT_SUMMARY);
+ mAttentionHelper.buzzBeepBlinkLocked(child, DEFAULT_SIGNALS);
+ verifyNeverBeep();
+ assertFalse(child.isInterruptive());
+ assertEquals(-1, child.getLastAudiblyAlertedMs());
+ Mockito.reset(mRingtonePlayer);
+
+ // child should beep at 0% volume
+ child = getBeepyNotificationRecord("a", GROUP_ALERT_SUMMARY);
+ mAttentionHelper.buzzBeepBlinkLocked(child, DEFAULT_SIGNALS);
+ verifyNeverBeep();
+ assertFalse(child.isInterruptive());
+ assertEquals(-1, child.getLastAudiblyAlertedMs());
+ Mockito.reset(mRingtonePlayer);
+
+ // summary 100% volume (GROUP_ALERT_SUMMARY)
+ NotificationRecord summary = getBeepyNotificationRecord("a", GROUP_ALERT_SUMMARY);
+ summary.getNotification().flags |= Notification.FLAG_GROUP_SUMMARY;
+ mAttentionHelper.buzzBeepBlinkLocked(summary, DEFAULT_SIGNALS);
+ assertNotEquals(-1, summary.getLastAudiblyAlertedMs());
+ verifyBeepVolume(1.0f);
+ Mockito.reset(mRingtonePlayer);
+
+ // next update at 50% volume because only summary was tracked as alerting
+ mAttentionHelper.buzzBeepBlinkLocked(summary, DEFAULT_SIGNALS);
+ assertNotEquals(-1, summary.getLastAudiblyAlertedMs());
+ verifyBeepVolume(0.5f);
+
+ verify(mAccessibilityService, times(4)).sendAccessibilityEvent(any(), anyInt());
+ }
+
+ @Test
+ public void testBeepVolume_politeNotif_groupAlertChildren() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+ mSetFlagsRule.disableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
+ TestableFlagResolver flagResolver = new TestableFlagResolver();
+ flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
+ flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
+ // NOTIFICATION_COOLDOWN_ALL setting is enabled
+ Settings.System.putInt(getContext().getContentResolver(),
+ Settings.System.NOTIFICATION_COOLDOWN_ALL, 1);
+ initAttentionHelper(flagResolver);
+
+ // summary 0% volume (GROUP_ALERT_CHILDREN)
+ NotificationRecord summary = getBeepyNotificationRecord("a", GROUP_ALERT_CHILDREN);
+ summary.getNotification().flags |= Notification.FLAG_GROUP_SUMMARY;
+ mAttentionHelper.buzzBeepBlinkLocked(summary, DEFAULT_SIGNALS);
+ verifyNeverBeep();
+ assertFalse(summary.isInterruptive());
+ assertEquals(-1, summary.getLastAudiblyAlertedMs());
+ Mockito.reset(mRingtonePlayer);
+
+ // child should beep at 100% volume
+ NotificationRecord child = getBeepyNotificationRecord("a", GROUP_ALERT_CHILDREN);
+ mAttentionHelper.buzzBeepBlinkLocked(child, DEFAULT_SIGNALS);
+ assertNotEquals(-1, child.getLastAudiblyAlertedMs());
+ verifyBeepVolume(1.0f);
+ Mockito.reset(mRingtonePlayer);
+
+ // child should beep at 50% volume
+ child = getBeepyNotificationRecord("a", GROUP_ALERT_CHILDREN);
+ mAttentionHelper.buzzBeepBlinkLocked(child, DEFAULT_SIGNALS);
+ assertNotEquals(-1, child.getLastAudiblyAlertedMs());
+ verifyBeepVolume(0.5f);
+ Mockito.reset(mRingtonePlayer);
+
+ // child should beep at 0% volume
+ mAttentionHelper.buzzBeepBlinkLocked(child, DEFAULT_SIGNALS);
+ verifyNeverBeep();
+ assertTrue(child.isInterruptive());
+ assertEquals(-1, child.getLastAudiblyAlertedMs());
+
+ verify(mAccessibilityService, times(4)).sendAccessibilityEvent(any(), anyInt());
+ }
+
+ @Test
public void testVibrationIntensity_politeNotif() throws Exception {
mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
TestableFlagResolver flagResolver = new TestableFlagResolver();
@@ -2914,8 +3024,9 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
Mockito.reset(vibratorHelper);
// 2nd update should buzz at 0% intensity
- mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
- verify(vibratorHelper, times(1)).scale(any(), eq(0.0f));
+ int buzzBeepBlink = mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+ verifyNeverVibrate();
+ assertThat(buzzBeepBlink).isEqualTo(MetricsEvent.ALERT_MUTED | MUTE_REASON_COOLDOWN);
}
@Test
@@ -3007,10 +3118,11 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase {
// 2nd update should beep at 0% volume
Mockito.reset(mRingtonePlayer);
- mAttentionHelper.buzzBeepBlinkLocked(r, WORK_PROFILE_SIGNALS);
- verifyBeepVolume(0.0f);
+ int buzzBeepBlink = mAttentionHelper.buzzBeepBlinkLocked(r, WORK_PROFILE_SIGNALS);
+ verifyNeverBeep();
+ assertThat(buzzBeepBlink).isEqualTo(MetricsEvent.ALERT_MUTED | MUTE_REASON_COOLDOWN);
- verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt());
+ verify(mAccessibilityService, times(2)).sendAccessibilityEvent(any(), anyInt());
assertEquals(-1, r.getLastAudiblyAlertedMs());
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 6c9015d72d5a..bbf2cbdbc145 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -193,6 +193,7 @@ import android.app.Notification.MessagingStyle.Message;
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
import android.app.NotificationManager;
+import android.app.NotificationManager.Policy;
import android.app.PendingIntent;
import android.app.Person;
import android.app.RemoteInput;
@@ -655,7 +656,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
when(mAtm.getTaskToShowPermissionDialogOn(anyString(), anyInt()))
.thenReturn(INVALID_TASK_ID);
mContext.addMockSystemService(AppOpsManager.class, mock(AppOpsManager.class));
- when(mUm.getProfileIds(eq(mUserId), eq(false))).thenReturn(new int[] { mUserId });
+ when(mUm.getProfileIds(eq(mUserId), anyBoolean())).thenReturn(new int[]{mUserId});
+ when(mUmInternal.getProfileIds(eq(mUserId), anyBoolean())).thenReturn(new int[]{mUserId});
when(mAmi.getCurrentUserId()).thenReturn(mUserId);
when(mPackageManagerClient.hasSystemFeature(FEATURE_TELECOM)).thenReturn(true);
@@ -4652,7 +4654,42 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
doThrow(new SecurityException("no access")).when(mUgmInternal)
.checkGrantUriPermission(eq(Process.myUid()), any(), eq(soundUri),
- anyInt(), eq(Process.myUserHandle().getIdentifier()));
+ anyInt(), eq(Process.myUserHandle().getIdentifier()));
+
+ mBinderService.updateNotificationChannelFromPrivilegedListener(
+ null, mPkg, Process.myUserHandle(), updatedNotificationChannel);
+
+ verify(mPreferencesHelper, times(1)).updateNotificationChannel(
+ anyString(), anyInt(), any(), anyBoolean(), anyInt(), anyBoolean());
+
+ verify(mListeners, never()).notifyNotificationChannelChanged(eq(mPkg),
+ eq(Process.myUserHandle()), eq(mTestNotificationChannel),
+ eq(NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED));
+ }
+
+ @Test
+ public void
+ testUpdateNotificationChannelFromPrivilegedListener_oldSoundNoUriPerm_newSoundHasUriPerm()
+ throws Exception {
+ mService.setPreferencesHelper(mPreferencesHelper);
+ when(mCompanionMgr.getAssociations(mPkg, mUserId))
+ .thenReturn(singletonList(mock(AssociationInfo.class)));
+ when(mPreferencesHelper.getNotificationChannel(eq(mPkg), anyInt(),
+ eq(mTestNotificationChannel.getId()), anyBoolean()))
+ .thenReturn(mTestNotificationChannel);
+
+ // Missing Uri permissions for the old channel sound
+ final Uri oldSoundUri = Settings.System.DEFAULT_NOTIFICATION_URI;
+ doThrow(new SecurityException("no access")).when(mUgmInternal)
+ .checkGrantUriPermission(eq(Process.myUid()), any(), eq(oldSoundUri),
+ anyInt(), eq(Process.myUserHandle().getIdentifier()));
+
+ // Has Uri permissions for the old channel sound
+ final Uri newSoundUri = Uri.parse("content://media/test/sound/uri");
+ final NotificationChannel updatedNotificationChannel = new NotificationChannel(
+ TEST_CHANNEL_ID, TEST_CHANNEL_ID, IMPORTANCE_DEFAULT);
+ updatedNotificationChannel.setSound(newSoundUri,
+ updatedNotificationChannel.getAudioAttributes());
mBinderService.updateNotificationChannelFromPrivilegedListener(
null, mPkg, Process.myUserHandle(), updatedNotificationChannel);
@@ -15936,6 +15973,57 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
assertThat(updatedRule.getValue().isEnabled()).isFalse();
}
+ @Test
+ @EnableFlags({android.app.Flags.FLAG_MODES_API, android.app.Flags.FLAG_MODES_UI})
+ public void setNotificationPolicy_fromSystemApp_appliesPriorityChannelsAllowed()
+ throws Exception {
+ setUpRealZenTest();
+ // Start with hasPriorityChannels=true, allowPriorityChannels=true ("default").
+ mService.mZenModeHelper.setNotificationPolicy(new Policy(0, 0, 0, 0,
+ Policy.policyState(true, true), 0),
+ ZenModeConfig.ORIGIN_SYSTEM, Process.SYSTEM_UID);
+
+ // The caller will supply states with "wrong" hasPriorityChannels.
+ int stateBlockingPriorityChannels = Policy.policyState(false, false);
+ mBinderService.setNotificationPolicy(mPkg,
+ new Policy(1, 0, 0, 0, stateBlockingPriorityChannels, 0), false);
+
+ // hasPriorityChannels is untouched and allowPriorityChannels was updated.
+ assertThat(mBinderService.getNotificationPolicy(mPkg).priorityCategories).isEqualTo(1);
+ assertThat(mBinderService.getNotificationPolicy(mPkg).state).isEqualTo(
+ Policy.policyState(true, false));
+
+ // Same but setting allowPriorityChannels to true.
+ int stateAllowingPriorityChannels = Policy.policyState(false, true);
+ mBinderService.setNotificationPolicy(mPkg,
+ new Policy(2, 0, 0, 0, stateAllowingPriorityChannels, 0), false);
+
+ assertThat(mBinderService.getNotificationPolicy(mPkg).priorityCategories).isEqualTo(2);
+ assertThat(mBinderService.getNotificationPolicy(mPkg).state).isEqualTo(
+ Policy.policyState(true, true));
+ }
+
+ @Test
+ @EnableFlags({android.app.Flags.FLAG_MODES_API, android.app.Flags.FLAG_MODES_UI})
+ @DisableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES)
+ public void setNotificationPolicy_fromRegularAppThatCanModifyPolicy_ignoresState()
+ throws Exception {
+ setUpRealZenTest();
+ // Start with hasPriorityChannels=true, allowPriorityChannels=true ("default").
+ mService.mZenModeHelper.setNotificationPolicy(new Policy(0, 0, 0, 0,
+ Policy.policyState(true, true), 0),
+ ZenModeConfig.ORIGIN_SYSTEM, Process.SYSTEM_UID);
+ mService.setCallerIsNormalPackage();
+
+ mBinderService.setNotificationPolicy(mPkg,
+ new Policy(1, 0, 0, 0, Policy.policyState(false, false), 0), false);
+
+ // Policy was updated but the attempt to change state was ignored (it's a @hide API).
+ assertThat(mBinderService.getNotificationPolicy(mPkg).priorityCategories).isEqualTo(1);
+ assertThat(mBinderService.getNotificationPolicy(mPkg).state).isEqualTo(
+ Policy.policyState(true, true));
+ }
+
/** Prepares for a zen-related test that uses the real {@link ZenModeHelper}. */
private void setUpRealZenTest() throws Exception {
when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt()))
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index a0c0df8853f9..d64b9e858c64 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -45,11 +45,13 @@ import static android.app.NotificationManager.IMPORTANCE_MAX;
import static android.app.NotificationManager.IMPORTANCE_NONE;
import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
import static android.app.NotificationManager.VISIBILITY_NO_OVERRIDE;
+import static android.content.ContentResolver.SCHEME_ANDROID_RESOURCE;
+import static android.content.ContentResolver.SCHEME_CONTENT;
+import static android.content.ContentResolver.SCHEME_FILE;
import static android.media.AudioAttributes.CONTENT_TYPE_SONIFICATION;
import static android.media.AudioAttributes.USAGE_NOTIFICATION;
import static android.os.UserHandle.USER_ALL;
import static android.os.UserHandle.USER_SYSTEM;
-
import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
import static android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION;
import static android.service.notification.Flags.notificationClassification;
@@ -59,6 +61,7 @@ import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_P
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED;
import static com.android.server.notification.Flags.FLAG_ALL_NOTIFS_NEED_TTL;
+import static com.android.server.notification.Flags.FLAG_NOTIFICATION_VERIFY_CHANNEL_SOUND_URI;
import static com.android.server.notification.Flags.FLAG_PERSIST_INCOMPLETE_RESTORE_DATA;
import static com.android.server.notification.NotificationChannelLogger.NotificationChannelEvent.NOTIFICATION_CHANNEL_UPDATED_BY_USER;
import static com.android.server.notification.PreferencesHelper.DEFAULT_BUBBLE_PREFERENCE;
@@ -84,6 +87,7 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
@@ -369,10 +373,10 @@ public class PreferencesHelperTest extends UiServiceTestCase {
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- false, mClock);
+ mUgmInternal, false, mClock);
mXmlHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- false, mClock);
+ mUgmInternal, false, mClock);
resetZenModeHelper();
mAudioAttributes = new AudioAttributes.Builder()
@@ -783,7 +787,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
public void testReadXml_oldXml_migrates() throws Exception {
mXmlHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- /* showReviewPermissionsNotification= */ true, mClock);
+ mUgmInternal, /* showReviewPermissionsNotification= */ true, mClock);
String xml = "<ranking version=\"2\">\n"
+ "<package name=\"" + PKG_N_MR1 + "\" uid=\"" + UID_N_MR1
@@ -919,7 +923,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
public void testReadXml_newXml_noMigration_showPermissionNotification() throws Exception {
mXmlHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- /* showReviewPermissionsNotification= */ true, mClock);
+ mUgmInternal, /* showReviewPermissionsNotification= */ true, mClock);
String xml = "<ranking version=\"3\">\n"
+ "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n"
@@ -978,7 +982,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
public void testReadXml_newXml_permissionNotificationOff() throws Exception {
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- /* showReviewPermissionsNotification= */ false, mClock);
+ mUgmInternal, /* showReviewPermissionsNotification= */ false, mClock);
String xml = "<ranking version=\"3\">\n"
+ "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n"
@@ -1037,7 +1041,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
public void testReadXml_newXml_noMigration_noPermissionNotification() throws Exception {
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- /* showReviewPermissionsNotification= */ true, mClock);
+ mUgmInternal, /* showReviewPermissionsNotification= */ true, mClock);
String xml = "<ranking version=\"4\">\n"
+ "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n"
@@ -1709,7 +1713,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
// simulate load after reboot
mXmlHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- false, mClock);
+ mUgmInternal, false, mClock);
loadByteArrayXml(baos.toByteArray(), false, USER_ALL);
// Trigger 2nd restore pass
@@ -1764,7 +1768,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
// simulate load after reboot
mXmlHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- false, mClock);
+ mUgmInternal, false, mClock);
loadByteArrayXml(xml.getBytes(), false, USER_ALL);
// Trigger 2nd restore pass
@@ -1842,10 +1846,10 @@ public class PreferencesHelperTest extends UiServiceTestCase {
mHelper = new PreferencesHelper(mContext, mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- false, mClock);
+ mUgmInternal, false, mClock);
mXmlHelper = new PreferencesHelper(mContext, mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
- false, mClock);
+ mUgmInternal, false, mClock);
NotificationChannel channel =
new NotificationChannel("id", "name", IMPORTANCE_LOW);
@@ -3049,6 +3053,64 @@ public class PreferencesHelperTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags(FLAG_NOTIFICATION_VERIFY_CHANNEL_SOUND_URI)
+ public void testCreateChannel_noSoundUriPermission_contentSchemeVerified() {
+ final Uri sound = Uri.parse(SCHEME_CONTENT + "://media/test/sound/uri");
+
+ doThrow(new SecurityException("no access")).when(mUgmInternal)
+ .checkGrantUriPermission(eq(UID_N_MR1), any(), eq(sound),
+ anyInt(), eq(Process.myUserHandle().getIdentifier()));
+
+ final NotificationChannel channel = new NotificationChannel("id2", "name2",
+ NotificationManager.IMPORTANCE_DEFAULT);
+ channel.setSound(sound, mAudioAttributes);
+
+ assertThrows(SecurityException.class,
+ () -> mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel,
+ true, false, UID_N_MR1, false));
+ assertThat(mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel.getId(), true))
+ .isNull();
+ }
+
+ @Test
+ @EnableFlags(FLAG_NOTIFICATION_VERIFY_CHANNEL_SOUND_URI)
+ public void testCreateChannel_noSoundUriPermission_fileSchemaIgnored() {
+ final Uri sound = Uri.parse(SCHEME_FILE + "://path/sound");
+
+ doThrow(new SecurityException("no access")).when(mUgmInternal)
+ .checkGrantUriPermission(eq(UID_N_MR1), any(), any(),
+ anyInt(), eq(Process.myUserHandle().getIdentifier()));
+
+ final NotificationChannel channel = new NotificationChannel("id2", "name2",
+ NotificationManager.IMPORTANCE_DEFAULT);
+ channel.setSound(sound, mAudioAttributes);
+
+ mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false, UID_N_MR1,
+ false);
+ assertThat(mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel.getId(), true)
+ .getSound()).isEqualTo(sound);
+ }
+
+ @Test
+ @EnableFlags(FLAG_NOTIFICATION_VERIFY_CHANNEL_SOUND_URI)
+ public void testCreateChannel_noSoundUriPermission_resourceSchemaIgnored() {
+ final Uri sound = Uri.parse(SCHEME_ANDROID_RESOURCE + "://resId/sound");
+
+ doThrow(new SecurityException("no access")).when(mUgmInternal)
+ .checkGrantUriPermission(eq(UID_N_MR1), any(), any(),
+ anyInt(), eq(Process.myUserHandle().getIdentifier()));
+
+ final NotificationChannel channel = new NotificationChannel("id2", "name2",
+ NotificationManager.IMPORTANCE_DEFAULT);
+ channel.setSound(sound, mAudioAttributes);
+
+ mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false, UID_N_MR1,
+ false);
+ assertThat(mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel.getId(), true)
+ .getSound()).isEqualTo(sound);
+ }
+
+ @Test
public void testPermanentlyDeleteChannels() throws Exception {
NotificationChannel channel1 =
new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
index 84c4f620f394..5709d884d427 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
@@ -1010,7 +1010,39 @@ public class ZenModeConfigTest extends UiServiceTestCase {
@Test
@EnableFlags(Flags.FLAG_MODES_UI)
- public void testConfigXml_manualRule_upgradeWhenExisting() throws Exception {
+ public void testConfigXml_manualRuleWithoutCondition_upgradeWhenExisting() throws Exception {
+ // prior to modes_ui, it's possible to have a non-null manual rule that doesn't have much
+ // data on it because it's meant to indicate that the manual rule is on by merely existing.
+ ZenModeConfig config = new ZenModeConfig();
+ config.manualRule = new ZenModeConfig.ZenRule();
+ config.manualRule.enabled = true;
+ config.manualRule.pkg = "android";
+ config.manualRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+ config.manualRule.conditionId = null;
+ config.manualRule.enabler = "test";
+
+ // write out entire config xml
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ writeConfigXml(config, XML_VERSION_MODES_API, /* forBackup= */ false, baos);
+ ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+ ZenModeConfig fromXml = readConfigXml(bais);
+
+
+ // The result should be valid and contain a manual rule; the rule should have a non-null
+ // ZenPolicy and a condition whose state is true. The conditionId should be default.
+ assertThat(fromXml.isValid()).isTrue();
+ assertThat(fromXml.manualRule).isNotNull();
+ assertThat(fromXml.manualRule.zenPolicy).isNotNull();
+ assertThat(fromXml.manualRule.condition).isNotNull();
+ assertThat(fromXml.manualRule.condition.state).isEqualTo(STATE_TRUE);
+ assertThat(fromXml.manualRule.conditionId).isEqualTo(Uri.EMPTY);
+ assertThat(fromXml.manualRule.enabler).isEqualTo("test");
+ assertThat(fromXml.isManualActive()).isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_UI)
+ public void testConfigXml_manualRuleWithCondition_upgradeWhenExisting() throws Exception {
// prior to modes_ui, it's possible to have a non-null manual rule that doesn't have much
// data on it because it's meant to indicate that the manual rule is on by merely existing.
ZenModeConfig config = new ZenModeConfig();
@@ -1029,6 +1061,7 @@ public class ZenModeConfigTest extends UiServiceTestCase {
// The result should have a manual rule; it should have a non-null ZenPolicy and a condition
// whose state is true. The conditionId and enabler data should also be preserved.
+ assertThat(fromXml.isValid()).isTrue();
assertThat(fromXml.manualRule).isNotNull();
assertThat(fromXml.manualRule.zenPolicy).isNotNull();
assertThat(fromXml.manualRule.condition).isNotNull();
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerTest.java
index 0d13be6d5ab2..e8ca8bf8ec63 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerTest.java
@@ -127,13 +127,13 @@ public class VibratorControllerTest {
public void setExternalControl_withCapability_enablesExternalControl() {
mockVibratorCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
VibratorController controller = createController();
- assertFalse(controller.isUnderExternalControl());
+ assertFalse(controller.isVibrating());
controller.setExternalControl(true);
- assertTrue(controller.isUnderExternalControl());
+ assertTrue(controller.isVibrating());
controller.setExternalControl(false);
- assertFalse(controller.isUnderExternalControl());
+ assertFalse(controller.isVibrating());
InOrder inOrderVerifier = inOrder(mNativeWrapperMock);
inOrderVerifier.verify(mNativeWrapperMock).setExternalControl(eq(true));
@@ -143,10 +143,10 @@ public class VibratorControllerTest {
@Test
public void setExternalControl_withNoCapability_ignoresExternalControl() {
VibratorController controller = createController();
- assertFalse(controller.isUnderExternalControl());
+ assertFalse(controller.isVibrating());
controller.setExternalControl(true);
- assertFalse(controller.isUnderExternalControl());
+ assertFalse(controller.isVibrating());
verify(mNativeWrapperMock, never()).setExternalControl(anyBoolean());
}
@@ -181,6 +181,38 @@ public class VibratorControllerTest {
}
@Test
+ public void setAmplitude_vibratorIdle_ignoresAmplitude() {
+ VibratorController controller = createController();
+ assertFalse(controller.isVibrating());
+
+ controller.setAmplitude(1);
+ assertEquals(0, controller.getCurrentAmplitude(), /* delta= */ 0);
+ }
+
+ @Test
+ public void setAmplitude_vibratorUnderExternalControl_ignoresAmplitude() {
+ mockVibratorCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
+ VibratorController controller = createController();
+ controller.setExternalControl(true);
+ assertTrue(controller.isVibrating());
+
+ controller.setAmplitude(1);
+ assertEquals(0, controller.getCurrentAmplitude(), /* delta= */ 0);
+ }
+
+ @Test
+ public void setAmplitude_vibratorVibrating_setsAmplitude() {
+ when(mNativeWrapperMock.on(anyLong(), anyLong())).thenAnswer(args -> args.getArgument(0));
+ VibratorController controller = createController();
+ controller.on(100, /* vibrationId= */ 1);
+ assertTrue(controller.isVibrating());
+ assertEquals(-1, controller.getCurrentAmplitude(), /* delta= */ 0);
+
+ controller.setAmplitude(1);
+ assertEquals(1, controller.getCurrentAmplitude(), /* delta= */ 0);
+ }
+
+ @Test
public void on_withDuration_turnsVibratorOn() {
when(mNativeWrapperMock.on(anyLong(), anyLong())).thenAnswer(args -> args.getArgument(0));
VibratorController controller = createController();
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index d99b20c689dd..538c3fc2ddae 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -266,12 +266,13 @@ public class VibratorManagerServiceTest {
@After
public void tearDown() throws Exception {
if (mService != null) {
- if (!mPendingVibrations.stream().allMatch(HalVibration::hasEnded)) {
- // Cancel any pending vibration from tests.
- cancelVibrate(mService);
- for (HalVibration vibration : mPendingVibrations) {
- vibration.waitForEnd();
- }
+ // Make sure we have permission to cancel test vibrations, even if the test denied them.
+ grantPermission(android.Manifest.permission.VIBRATE);
+ // Cancel any pending vibration from tests, including external vibrations.
+ cancelVibrate(mService);
+ // Wait until pending vibrations end asynchronously.
+ for (HalVibration vibration : mPendingVibrations) {
+ vibration.waitForEnd();
}
// Wait until all vibrators have stopped vibrating, waiting for ramp-down.
// Note: if a test is flaky here something is wrong with the vibration finalization.
@@ -2242,7 +2243,7 @@ public class VibratorManagerServiceTest {
VibratorManagerService service = createSystemReadyService();
VibrationEffect effect = VibrationEffect.createOneShot(10 * TEST_TIMEOUT_MILLIS, 100);
- vibrate(service, effect, HAPTIC_FEEDBACK_ATTRS);
+ HalVibration vibration = vibrate(service, effect, HAPTIC_FEEDBACK_ATTRS);
// VibrationThread will start this vibration async, so wait until vibration is triggered.
assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
@@ -2255,7 +2256,8 @@ public class VibratorManagerServiceTest {
assertNotEquals(ExternalVibrationScale.ScaleLevel.SCALE_MUTE, scale.scaleLevel);
// Vibration is cancelled.
- assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+ vibration.waitForEnd();
+ assertThat(vibration.getStatus()).isEqualTo(Status.CANCELLED_SUPERSEDED);
assertEquals(Arrays.asList(false, true),
mVibratorProviders.get(1).getExternalControlStates());
}
@@ -2296,7 +2298,7 @@ public class VibratorManagerServiceTest {
VibrationEffect repeatingEffect = VibrationEffect.createWaveform(
new long[]{100, 200, 300}, new int[]{128, 255, 255}, 1);
- vibrate(service, repeatingEffect, ALARM_ATTRS);
+ HalVibration repeatingVibration = vibrate(service, repeatingEffect, ALARM_ATTRS);
// VibrationThread will start this vibration async, so wait until vibration is triggered.
assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
@@ -2308,7 +2310,8 @@ public class VibratorManagerServiceTest {
assertNotEquals(ExternalVibrationScale.ScaleLevel.SCALE_MUTE, scale.scaleLevel);
// Vibration is cancelled.
- assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+ repeatingVibration.waitForEnd();
+ assertThat(repeatingVibration.getStatus()).isEqualTo(Status.CANCELLED_SUPERSEDED);
assertEquals(Arrays.asList(false, true),
mVibratorProviders.get(1).getExternalControlStates());
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java
index 5787780cef46..4cd75d5ba074 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java
@@ -308,6 +308,8 @@ public class ActivityOptionsTest {
// KEY_PENDING_INTENT_CREATOR_BACKGROUND_ACTIVITY_START_MODE
case "android.activity.launchCookie": // KEY_LAUNCH_COOKIE
case "android:activity.animAbortListener": // KEY_ANIM_ABORT_LISTENER
+ case "android.activity.allowPassThroughOnTouchOutside":
+ // KEY_ALLOW_PASS_THROUGH_ON_TOUCH_OUTSIDE
// Existing keys
break;
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
index 92205f391f32..65736cbc519f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
@@ -186,7 +186,8 @@ class AppCompatActivityRobot {
void setTopActivityCameraActive(boolean enabled) {
doReturn(enabled).when(getTopDisplayRotationCompatPolicy())
- .isCameraActive(eq(mActivityStack.top()), /* mustBeFullscreen= */ eq(true));
+ .isCameraRunningAndWindowingModeEligible(eq(mActivityStack.top()),
+ /* mustBeFullscreen= */ eq(true));
}
void setTopActivityEligibleForOrientationOverride(boolean enabled) {
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 d66c21a77fcd..b91a5b7afe26 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraOverridesTest.java
@@ -28,7 +28,7 @@ import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_V
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-import static com.android.window.flags.Flags.FLAG_CAMERA_COMPAT_FOR_FREEFORM;
+import static com.android.window.flags.Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING;
import android.compat.testing.PlatformCompatChangeRule;
import android.platform.test.annotations.DisableFlags;
@@ -218,7 +218,7 @@ public class AppCompatCameraOverridesTest extends WindowTestsBase {
}
@Test
- @DisableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM)
+ @DisableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
public void testShouldApplyCameraCompatFreeformTreatment_flagIsDisabled_returnsFalse() {
runTestScenario((robot) -> {
robot.activity().createActivityWithComponentInNewTask();
@@ -229,7 +229,7 @@ public class AppCompatCameraOverridesTest extends WindowTestsBase {
@Test
@EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT})
- @EnableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM)
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
public void testShouldApplyCameraCompatFreeformTreatment_overrideEnabled_returnsFalse() {
runTestScenario((robot) -> {
robot.activity().createActivityWithComponentInNewTask();
@@ -240,7 +240,7 @@ public class AppCompatCameraOverridesTest extends WindowTestsBase {
@Test
@EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT})
- @EnableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM)
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
public void testShouldApplyCameraCompatFreeformTreatment_disabledByOverride_returnsFalse() {
runTestScenario((robot) -> {
robot.activity().createActivityWithComponentInNewTask();
@@ -250,7 +250,7 @@ public class AppCompatCameraOverridesTest extends WindowTestsBase {
}
@Test
- @EnableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM)
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
public void testShouldApplyCameraCompatFreeformTreatment_notDisabledByOverride_returnsTrue() {
runTestScenario((robot) -> {
robot.activity().createActivityWithComponentInNewTask();
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraPolicyTest.java
index d91b38efd40b..41102d6922da 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatCameraPolicyTest.java
@@ -20,7 +20,7 @@ import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_ONLY_FOR
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-import static com.android.window.flags.Flags.FLAG_CAMERA_COMPAT_FOR_FREEFORM;
+import static com.android.window.flags.Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -85,7 +85,7 @@ public class AppCompatCameraPolicyTest extends WindowTestsBase {
}
@Test
- @EnableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM)
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
public void testCameraCompatFreeformPolicy_presentWhenEnabledAndDW() {
runTestScenario((robot) -> {
robot.allowEnterDesktopMode(/* isAllowed= */ true);
@@ -95,7 +95,7 @@ public class AppCompatCameraPolicyTest extends WindowTestsBase {
}
@Test
- @EnableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM)
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
public void testCameraCompatFreeformPolicy_notPresentWhenNoDW() {
runTestScenario((robot) -> {
robot.allowEnterDesktopMode(/* isAllowed= */ false);
@@ -105,7 +105,7 @@ public class AppCompatCameraPolicyTest extends WindowTestsBase {
}
@Test
- @DisableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM)
+ @DisableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
public void testCameraCompatFreeformPolicy_notPresentWhenNoFlag() {
runTestScenario((robot) -> {
robot.allowEnterDesktopMode(/* isAllowed= */ true);
@@ -115,7 +115,7 @@ public class AppCompatCameraPolicyTest extends WindowTestsBase {
}
@Test
- @EnableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM)
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
public void testCameraCompatFreeformPolicy_notPresentWhenNoFlagAndNoDW() {
runTestScenario((robot) -> {
robot.allowEnterDesktopMode(/* isAllowed= */ false);
@@ -125,7 +125,7 @@ public class AppCompatCameraPolicyTest extends WindowTestsBase {
}
@Test
- @EnableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM)
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
public void testCameraCompatFreeformPolicy_startedWhenEnabledAndDW() {
runTestScenario((robot) -> {
robot.allowEnterDesktopMode(/* isAllowed= */ true);
@@ -136,7 +136,7 @@ public class AppCompatCameraPolicyTest extends WindowTestsBase {
}
@Test
- @EnableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM)
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
public void testCameraStateManager_existsWhenCameraCompatFreeformExists() {
runTestScenario((robot) -> {
robot.allowEnterDesktopMode(true);
@@ -147,7 +147,7 @@ public class AppCompatCameraPolicyTest extends WindowTestsBase {
}
@Test
- @EnableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM)
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
public void testCameraStateManager_startedWhenCameraCompatFreeformExists() {
runTestScenario((robot) -> {
robot.allowEnterDesktopMode(true);
@@ -180,7 +180,7 @@ public class AppCompatCameraPolicyTest extends WindowTestsBase {
}
@Test
- @DisableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM)
+ @DisableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
public void testCameraStateManager_doesNotExistWhenNoPolicyExists() {
runTestScenario((robot) -> {
robot.conf().enableCameraCompatTreatmentAtBuildTime(/* enabled= */ false);
diff --git a/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
index a48813d775d1..a8ccf95e1bb5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
@@ -26,6 +26,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE;
import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP;
import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_FULL_USER;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
@@ -35,7 +36,8 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
-import static com.android.window.flags.Flags.FLAG_CAMERA_COMPAT_FOR_FREEFORM;
+import static com.android.server.wm.AppCompatConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO;
+import static com.android.window.flags.Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -60,6 +62,7 @@ import android.content.res.Configuration.Orientation;
import android.graphics.Rect;
import android.hardware.camera2.CameraManager;
import android.os.Handler;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.view.DisplayInfo;
import android.view.Surface;
@@ -135,7 +138,6 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
});
mActivityRefresher = new ActivityRefresher(mDisplayContent.mWmService, mMockHandler);
- mSetFlagsRule.enableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM);
CameraStateMonitor cameraStateMonitor =
new CameraStateMonitor(mDisplayContent, mMockHandler);
mCameraCompatFreeformPolicy =
@@ -147,6 +149,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
cameraStateMonitor.startListeningToCameraState();
}
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
@Test
public void testFullscreen_doesNotActivateCameraCompatMode() {
configureActivity(SCREEN_ORIENTATION_PORTRAIT, WINDOWING_MODE_FULLSCREEN);
@@ -157,6 +160,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
assertNotInCameraCompatMode();
}
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
@Test
public void testOrientationUnspecified_doesNotActivateCameraCompatMode() {
configureActivity(SCREEN_ORIENTATION_UNSPECIFIED);
@@ -164,12 +168,14 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
assertNotInCameraCompatMode();
}
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
@Test
public void testNoCameraConnection_doesNotActivateCameraCompatMode() {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
assertNotInCameraCompatMode();
}
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
@Test
public void testCameraConnected_deviceInPortrait_portraitCameraCompatMode() throws Exception {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
@@ -210,6 +216,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
assertActivityRefreshRequested(/* refreshRequested */ false);
}
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
@Test
public void testCameraReconnected_cameraCompatModeAndRefresh() throws Exception {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
@@ -235,25 +242,28 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
}
@Test
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
@EnableCompatChanges({OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT})
public void testShouldApplyCameraCompatFreeformTreatment_overrideEnabled_returnsFalse() {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
assertTrue(mActivity.info
.isChangeEnabled(OVERRIDE_CAMERA_COMPAT_DISABLE_FREEFORM_WINDOWING_TREATMENT));
- assertFalse(mCameraCompatFreeformPolicy
- .shouldApplyFreeformTreatmentForCameraCompat(mActivity));
+ assertFalse(mCameraCompatFreeformPolicy.isCameraCompatForFreeformEnabledForActivity(
+ mActivity));
}
@Test
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
public void testShouldApplyCameraCompatFreeformTreatment_notDisabledByOverride_returnsTrue() {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- assertTrue(mCameraCompatFreeformPolicy
- .shouldApplyFreeformTreatmentForCameraCompat(mActivity));
+ assertTrue(mCameraCompatFreeformPolicy.isCameraCompatForFreeformEnabledForActivity(
+ mActivity));
}
@Test
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
public void testOnActivityConfigurationChanging_refreshDisabledViaFlag_noRefresh()
throws Exception {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
@@ -268,6 +278,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
}
@Test
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
public void testOnActivityConfigurationChanging_cycleThroughStopDisabled() throws Exception {
when(mAppCompatConfiguration.isCameraCompatRefreshCycleThroughStopEnabled())
.thenReturn(false);
@@ -281,6 +292,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
}
@Test
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
public void testOnActivityConfigurationChanging_cycleThroughStopDisabledForApp()
throws Exception {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
@@ -293,6 +305,49 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
assertActivityRefreshRequested(/* refreshRequested */ true, /* cycleThroughStop */ false);
}
+ @Test
+ public void testGetCameraCompatAspectRatio_activityNotInCameraCompat_returnsDefaultAspRatio() {
+ configureActivity(SCREEN_ORIENTATION_FULL_USER);
+
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ callOnActivityConfigurationChanging(mActivity);
+
+ assertEquals(MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO,
+ mCameraCompatFreeformPolicy.getCameraCompatAspectRatio(mActivity),
+ /* delta= */ 0.001);
+ }
+
+ @Test
+ public void testGetCameraCompatAspectRatio_activityInCameraCompat_returnsConfigAspectRatio() {
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+ final float configAspectRatio = 1.5f;
+ mWm.mAppCompatConfiguration.setCameraCompatAspectRatio(configAspectRatio);
+
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ callOnActivityConfigurationChanging(mActivity);
+
+ assertEquals(configAspectRatio,
+ mCameraCompatFreeformPolicy.getCameraCompatAspectRatio(mActivity),
+ /* delta= */ 0.001);
+ }
+
+
+ @Test
+ public void testGetCameraCompatAspectRatio_inCameraCompatPerAppOverride_returnDefAspectRatio() {
+ configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+ final float configAspectRatio = 1.5f;
+ mWm.mAppCompatConfiguration.setCameraCompatAspectRatio(configAspectRatio);
+ doReturn(true).when(mActivity.mAppCompatController.getAppCompatCameraOverrides())
+ .isOverrideMinAspectRatioForCameraEnabled();
+
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ callOnActivityConfigurationChanging(mActivity);
+
+ assertEquals(MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO,
+ mCameraCompatFreeformPolicy.getCameraCompatAspectRatio(mActivity),
+ /* delta= */ 0.001);
+ }
+
private void configureActivity(@ScreenOrientation int activityOrientation) {
configureActivity(activityOrientation, WINDOWING_MODE_FREEFORM);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 85cb1bcc01fb..5c0d424f4f42 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -83,7 +83,7 @@ import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_TOKEN_TRANSFO
import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
-import static com.android.window.flags.Flags.FLAG_CAMERA_COMPAT_FOR_FREEFORM;
+import static com.android.window.flags.Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING;
import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE;
import static com.google.common.truth.Truth.assertThat;
@@ -2820,7 +2820,7 @@ public class DisplayContentTests extends WindowTestsBase {
verify(mWm.mUmInternal, never()).isUserVisible(userId2, displayId);
}
- @EnableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM)
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
@Test
public void cameraCompatFreeformFlagEnabled_cameraCompatFreeformPolicyNotNull() {
doReturn(true).when(() ->
@@ -2829,7 +2829,7 @@ public class DisplayContentTests extends WindowTestsBase {
assertTrue(createNewDisplay().mAppCompatCameraPolicy.hasCameraCompatFreeformPolicy());
}
- @DisableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM)
+ @DisableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
@Test
public void cameraCompatFreeformFlagNotEnabled_cameraCompatFreeformPolicyIsNull() {
doReturn(true).when(() ->
@@ -2838,7 +2838,7 @@ public class DisplayContentTests extends WindowTestsBase {
assertFalse(createNewDisplay().mAppCompatCameraPolicy.hasCameraCompatFreeformPolicy());
}
- @EnableFlags(FLAG_CAMERA_COMPAT_FOR_FREEFORM)
+ @EnableFlags(FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING)
@DisableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
@Test
public void desktopWindowingFlagNotEnabled_cameraCompatFreeformPolicyIsNull() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
index 7a0961d8c306..1015651438c3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
@@ -84,6 +84,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase {
// Disabling this call for most tests since it can override the systemUiFlags when called.
doNothing().when(mDisplayPolicy).updateSystemBarAttributes();
+ makeWindowVisible(mStatusBarWindow, mNavBarWindow);
updateDisplayFrames();
}
@@ -154,6 +155,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase {
};
addWindow(win);
win.getFrame().set(0, 0, 500, 100);
+ makeWindowVisible(win);
win.updateSourceFrame(win.getFrame());
mDisplayContent.getInsetsStateController().onPostLayout();
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index 6a89178ec9bf..3bd57475614f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -540,6 +540,7 @@ public class DisplayPolicyTests extends WindowTestsBase {
final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy();
mDisplayContent.setInputMethodWindowLocked(mImeWindow);
+ makeWindowVisible(mImeWindow);
mImeWindow.getControllableInsetProvider().setServerVisible(true);
mImeWindow.mGivenContentInsets.set(0, 10, 0, 0);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
index 8cf593fd21db..35c9e3fb3aaf 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
@@ -544,39 +544,35 @@ public final class DisplayRotationCompatPolicyTests extends WindowTestsBase {
}
@Test
- public void testIsCameraActiveWhenCallbackInvokedNoMultiWindow_returnTrue() {
+ public void testShouldCameraCompatControlOrientationWhenInvokedNoMultiWindow_returnTrue() {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
- assertTrue(
- mDisplayRotationCompatPolicy.isCameraActive(mActivity, /* mustBeFullscreen*/ true));
+ assertTrue(mDisplayRotationCompatPolicy.shouldCameraCompatControlOrientation(mActivity));
}
@Test
- public void testIsCameraActiveWhenCallbackNotInvokedNoMultiWindow_returnFalse() {
+ public void testShouldCameraCompatControlOrientationWhenNotInvokedNoMultiWindow_returnFalse() {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- assertFalse(
- mDisplayRotationCompatPolicy.isCameraActive(mActivity, /* mustBeFullscreen*/ true));
+ assertFalse(mDisplayRotationCompatPolicy.shouldCameraCompatControlOrientation(mActivity));
}
@Test
- public void testIsCameraActiveWhenCallbackNotInvokedMultiWindow_returnFalse() {
+ public void testShouldCameraCompatControlOrientationWhenNotInvokedMultiWindow_returnFalse() {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
when(mActivity.inMultiWindowMode()).thenReturn(true);
- assertFalse(
- mDisplayRotationCompatPolicy.isCameraActive(mActivity, /* mustBeFullscreen*/ true));
+ assertFalse(mDisplayRotationCompatPolicy.shouldCameraCompatControlOrientation(mActivity));
}
@Test
- public void testIsCameraActiveWhenCallbackInvokedMultiWindow_returnFalse() {
+ public void testShouldCameraCompatControlOrientationWhenInvokedMultiWindow_returnFalse() {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
when(mActivity.inMultiWindowMode()).thenReturn(true);
mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
- assertFalse(
- mDisplayRotationCompatPolicy.isCameraActive(mActivity, /* mustBeFullscreen*/ true));
+ assertFalse(mDisplayRotationCompatPolicy.shouldCameraCompatControlOrientation(mActivity));
}
private void configureActivity(@ScreenOrientation int activityOrientation) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
index d0d7c06bd706..66a66a1e358b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
@@ -291,6 +291,8 @@ public class InsetsStateControllerTest extends WindowTestsBase {
final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
final WindowState ime = createWindow(null, TYPE_INPUT_METHOD, "ime");
+ makeWindowVisible(statusBar);
+
// IME cannot be the IME target.
ime.mAttrs.flags |= FLAG_NOT_FOCUSABLE;
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 8fa4667c3b24..adc969c40e35 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -4849,6 +4849,39 @@ public class SizeCompatTests extends WindowTestsBase {
}
@Test
+ public void testUniversalResizeable() {
+ mWm.mConstants.mIgnoreActivityOrientationRequest = true;
+ setUpApp(mDisplayContent);
+ final float maxAspect = 1.8f;
+ final float minAspect = 1.5f;
+ prepareLimitedBounds(mActivity, maxAspect, minAspect,
+ ActivityInfo.SCREEN_ORIENTATION_LOCKED, true /* isUnresizable */);
+
+ assertTrue(mActivity.isUniversalResizeable());
+ assertTrue(mActivity.isResizeable());
+ assertFalse(mActivity.shouldCreateAppCompatDisplayInsets());
+ assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, mActivity.getOverrideOrientation());
+ assertEquals(mActivity.getTask().getBounds(), mActivity.getBounds());
+ final AppCompatAspectRatioPolicy aspectRatioPolicy = mActivity.mAppCompatController
+ .getAppCompatAspectRatioPolicy();
+ assertEquals(0, aspectRatioPolicy.getMaxAspectRatio(), 0 /* delta */);
+ assertEquals(0, aspectRatioPolicy.getMinAspectRatio(), 0 /* delta */);
+
+ // Compat override can still take effect.
+ final AppCompatAspectRatioOverrides aspectRatioOverrides =
+ mActivity.mAppCompatController.getAppCompatAspectRatioOverrides();
+ spyOn(aspectRatioOverrides);
+ doReturn(true).when(aspectRatioOverrides).shouldOverrideMinAspectRatio();
+ assertEquals(minAspect, aspectRatioPolicy.getMinAspectRatio(), 0 /* delta */);
+
+ // User override can still take effect.
+ doReturn(true).when(aspectRatioOverrides).shouldApplyUserMinAspectRatioOverride();
+ assertFalse(mActivity.isResizeable());
+ assertEquals(maxAspect, aspectRatioPolicy.getMaxAspectRatio(), 0 /* delta */);
+ assertNotEquals(SCREEN_ORIENTATION_UNSPECIFIED, mActivity.getOverrideOrientation());
+ }
+
+ @Test
public void testClearSizeCompat_resetOverrideConfig() {
final int origDensity = 480;
final int newDensity = 520;
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 2d5e5dacc217..e7e184c537f7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -483,6 +483,32 @@ public class WindowStateTests extends WindowTestsBase {
assertFalse(statusBar.isVisible());
}
+ /**
+ * Verifies that the InsetsSourceProvider frame cannot be updated by WindowState before
+ * relayout is called.
+ */
+ @SetupWindows(addWindows = { W_STATUS_BAR })
+ @Test
+ public void testUpdateSourceFrameBeforeRelayout() {
+ final WindowState statusBar = mStatusBarWindow;
+ statusBar.mHasSurface = true;
+ assertTrue(statusBar.isVisible());
+ final int statusBarId = InsetsSource.createId(null, 0, statusBars());
+ final var statusBarProvider = mDisplayContent.getInsetsStateController()
+ .getOrCreateSourceProvider(statusBarId, statusBars());
+ statusBarProvider.setWindowContainer(statusBar, null /* frameProvider */,
+ null /* imeFrameProvider */);
+
+ statusBar.updateSourceFrame(new Rect(0, 0, 500, 200));
+ assertTrue("InsetsSourceProvider frame should not be updated before relayout",
+ statusBarProvider.getSourceFrame().isEmpty());
+
+ makeWindowVisible(statusBar);
+ statusBar.updateSourceFrame(new Rect(0, 0, 500, 100));
+ assertEquals("InsetsSourceProvider frame should be updated after relayout",
+ new Rect(0, 0, 500, 100), statusBarProvider.getSourceFrame());
+ }
+
@Test
public void testIsSelfOrAncestorWindowAnimating() {
final WindowState root = createWindow(null, TYPE_APPLICATION, "root");
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index 6c1e1a428fb8..129494517cd6 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -1027,28 +1027,36 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser
boolean enabled = (mCurrentFunctions & UsbManager.FUNCTION_MIDI) != 0;
if (enabled != mMidiEnabled) {
if (enabled) {
+ boolean midiDeviceFound = false;
if (android.hardware.usb.flags.Flags.enableUsbSysfsMidiIdentification()) {
try {
getMidiCardDevice();
+ midiDeviceFound = true;
} catch (FileNotFoundException e) {
- Slog.e(TAG, "could not identify MIDI device", e);
- enabled = false;
+ Slog.w(TAG, "could not identify MIDI device", e);
}
- } else {
+ }
+ // For backward compatibility with older kernels without
+ // https://lore.kernel.org/r/20240307030922.3573161-1-royluo@google.com
+ if (!midiDeviceFound) {
Scanner scanner = null;
try {
scanner = new Scanner(new File(MIDI_ALSA_PATH));
mMidiCard = scanner.nextInt();
mMidiDevice = scanner.nextInt();
+ midiDeviceFound = true;
} catch (FileNotFoundException e) {
Slog.e(TAG, "could not open MIDI file", e);
- enabled = false;
} finally {
if (scanner != null) {
scanner.close();
}
}
}
+ if (!midiDeviceFound) {
+ Slog.e(TAG, "Failed to enable MIDI function");
+ enabled = false;
+ }
}
mMidiEnabled = enabled;
}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 3e226ccf2737..92effe05882a 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -19426,4 +19426,48 @@ public class TelephonyManager {
return "UNKNOWN(" + state + ")";
}
}
+
+ /**
+ * This API can be used by only CTS to override the Euicc UI component.
+ *
+ * @param componentName ui component to be launched for testing. {@code null} to reset.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ public void setTestEuiccUiComponent(@Nullable ComponentName componentName) {
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony == null) {
+ Rlog.e(TAG, "setTestEuiccUiComponent(): ITelephony instance is NULL");
+ throw new IllegalStateException("Telephony service not available.");
+ }
+ telephony.setTestEuiccUiComponent(componentName);
+ } catch (RemoteException ex) {
+ Rlog.e(TAG, "setTestEuiccUiComponent() RemoteException : " + ex);
+ throw ex.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
+ * This API can be used by only CTS to retrieve the Euicc UI component.
+ *
+ * @return The Euicc UI component for testing. {@code null} if not available.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ @Nullable
+ public ComponentName getTestEuiccUiComponent() {
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony == null) {
+ Rlog.e(TAG, "getTestEuiccUiComponent(): ITelephony instance is NULL");
+ throw new IllegalStateException("Telephony service not available.");
+ }
+ return telephony.getTestEuiccUiComponent();
+ } catch (RemoteException ex) {
+ Rlog.e(TAG, "getTestEuiccUiComponent() RemoteException : " + ex);
+ throw ex.rethrowAsRuntimeException();
+ }
+ }
}
diff --git a/telephony/java/android/telephony/UiccAccessRule.java b/telephony/java/android/telephony/UiccAccessRule.java
index 38b551bff9a7..65e325466108 100644
--- a/telephony/java/android/telephony/UiccAccessRule.java
+++ b/telephony/java/android/telephony/UiccAccessRule.java
@@ -169,18 +169,21 @@ public final class UiccAccessRule implements Parcelable {
}
private final byte[] mCertificateHash;
+ private final int mCertificateHashHashCode;
private final @Nullable String mPackageName;
// This bit is not currently used, but reserved for future use.
private final long mAccessType;
public UiccAccessRule(byte[] certificateHash, @Nullable String packageName, long accessType) {
this.mCertificateHash = certificateHash;
+ this.mCertificateHashHashCode = getCertificateHashHashCode(this.mCertificateHash);
this.mPackageName = packageName;
this.mAccessType = accessType;
}
UiccAccessRule(Parcel in) {
mCertificateHash = in.createByteArray();
+ mCertificateHashHashCode = getCertificateHashHashCode(mCertificateHash);
mPackageName = in.readString();
mAccessType = in.readLong();
}
@@ -247,7 +250,7 @@ public final class UiccAccessRule implements Parcelable {
public int getCarrierPrivilegeStatus(Signature signature, String packageName) {
byte[] certHash256 = getCertHash(signature, "SHA-256");
// Check SHA-256 first as it's the new standard.
- if (matches(certHash256, packageName)) {
+ if (hasMatchingCertificateHashAndPackageName(certHash256, packageName)) {
return TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
}
@@ -255,7 +258,7 @@ public final class UiccAccessRule implements Parcelable {
// in the near future when GPD_SPE_068 fully replaces GPD_SPE_013.
if (this.mCertificateHash.length == 20) {
byte[] certHash = getCertHash(signature, "SHA-1");
- if (matches(certHash, packageName)) {
+ if (hasMatchingCertificateHashAndPackageName(certHash, packageName)) {
return TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
}
}
@@ -267,15 +270,41 @@ public final class UiccAccessRule implements Parcelable {
* Returns true if the given certificate and package name match this rule's values.
* @hide
*/
- public boolean matches(@Nullable String certHash, @Nullable String packageName) {
- return matches(IccUtils.hexStringToBytes(certHash), packageName);
+ public boolean hasMatchingCertificateHashAndPackageName(
+ @Nullable String certHash, @Nullable String packageName) {
+ return hasMatchingCertificateHashAndPackageName(
+ IccUtils.hexStringToBytes(certHash), packageName);
}
- private boolean matches(byte[] certHash, String packageName) {
+ /**
+ * Returns true if the given certificate and package name match this rule's values.
+ * @hide
+ */
+ public boolean hasMatchingCertificateHashAndPackageName(
+ @Nullable byte[] certHash, @Nullable String packageName) {
return certHash != null && Arrays.equals(this.mCertificateHash, certHash) &&
(TextUtils.isEmpty(this.mPackageName) || this.mPackageName.equals(packageName));
}
+ /**
+ * Returns true if the given certificate hash hash
+ * and package name both match this rules' values.
+ *
+ * @hide
+ */
+ public boolean hasMatchingCertificateHashHashAndPackageName(
+ int certHashHashCode, String packageName) {
+ return certHashHashCode == this.mCertificateHashHashCode
+ && (TextUtils.isEmpty(this.mPackageName) || this.mPackageName.equals(packageName));
+ }
+
+ /**
+ * @hide
+ */
+ public static int getCertificateHashHashCode(byte[] certHash) {
+ return Arrays.hashCode(certHash);
+ }
+
@Override
public boolean equals(@Nullable Object obj) {
if (this == obj) {
diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java
index 44d3fca6aec6..567314beadd3 100644
--- a/telephony/java/android/telephony/data/ApnSetting.java
+++ b/telephony/java/android/telephony/data/ApnSetting.java
@@ -128,6 +128,12 @@ public class ApnSetting implements Parcelable {
/** APN type for RCS (Rich Communication Services). */
@FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
public static final int TYPE_RCS = ApnTypes.RCS;
+ /** APN type for OEM_PAID networks (Automotive PANS) */
+ @FlaggedApi(Flags.FLAG_OEM_PAID_PRIVATE)
+ public static final int TYPE_OEM_PAID = 1 << 16; // TODO(b/366194627): ApnTypes.OEM_PAID;
+ /** APN type for OEM_PRIVATE networks (Automotive PANS) */
+ @FlaggedApi(Flags.FLAG_OEM_PAID_PRIVATE)
+ public static final int TYPE_OEM_PRIVATE = 1 << 17; // TODO(b/366194627): ApnTypes.OEM_PRIVATE;
/** @hide */
@IntDef(flag = true, prefix = {"TYPE_"}, value = {
@@ -146,7 +152,9 @@ public class ApnSetting implements Parcelable {
TYPE_BIP,
TYPE_VSIM,
TYPE_ENTERPRISE,
- TYPE_RCS
+ TYPE_RCS,
+ TYPE_OEM_PAID,
+ TYPE_OEM_PRIVATE,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ApnType {
@@ -375,6 +383,27 @@ public class ApnSetting implements Parcelable {
@SystemApi
public static final String TYPE_RCS_STRING = "rcs";
+ /**
+ * APN type for OEM_PAID networks (Automotive PANS)
+ *
+ * Note: String representations of APN types are intended for system apps to communicate with
+ * modem components or carriers. Non-system apps should use the integer variants instead.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_OEM_PAID_PRIVATE)
+ @SystemApi
+ public static final String TYPE_OEM_PAID_STRING = "oem_paid";
+
+ /**
+ * APN type for OEM_PRIVATE networks (Automotive PANS)
+ *
+ * Note: String representations of APN types are intended for system apps to communicate with
+ * modem components or carriers. Non-system apps should use the integer variants instead.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_OEM_PAID_PRIVATE)
+ @SystemApi
+ public static final String TYPE_OEM_PRIVATE_STRING = "oem_private";
/** @hide */
@IntDef(prefix = { "AUTH_TYPE_" }, value = {
@@ -489,6 +518,8 @@ public class ApnSetting implements Parcelable {
APN_TYPE_STRING_MAP.put(TYPE_VSIM_STRING, TYPE_VSIM);
APN_TYPE_STRING_MAP.put(TYPE_BIP_STRING, TYPE_BIP);
APN_TYPE_STRING_MAP.put(TYPE_RCS_STRING, TYPE_RCS);
+ APN_TYPE_STRING_MAP.put(TYPE_OEM_PAID_STRING, TYPE_OEM_PAID);
+ APN_TYPE_STRING_MAP.put(TYPE_OEM_PRIVATE_STRING, TYPE_OEM_PRIVATE);
APN_TYPE_INT_MAP = new ArrayMap<>();
APN_TYPE_INT_MAP.put(TYPE_DEFAULT, TYPE_DEFAULT_STRING);
@@ -507,6 +538,8 @@ public class ApnSetting implements Parcelable {
APN_TYPE_INT_MAP.put(TYPE_VSIM, TYPE_VSIM_STRING);
APN_TYPE_INT_MAP.put(TYPE_BIP, TYPE_BIP_STRING);
APN_TYPE_INT_MAP.put(TYPE_RCS, TYPE_RCS_STRING);
+ APN_TYPE_INT_MAP.put(TYPE_OEM_PAID, TYPE_OEM_PAID_STRING);
+ APN_TYPE_INT_MAP.put(TYPE_OEM_PRIVATE, TYPE_OEM_PRIVATE_STRING);
PROTOCOL_STRING_MAP = new ArrayMap<>();
PROTOCOL_STRING_MAP.put("IP", PROTOCOL_IP);
@@ -2383,7 +2416,8 @@ public class ApnSetting implements Parcelable {
public ApnSetting build() {
if ((mApnTypeBitmask & (TYPE_DEFAULT | TYPE_MMS | TYPE_SUPL | TYPE_DUN | TYPE_HIPRI
| TYPE_FOTA | TYPE_IMS | TYPE_CBS | TYPE_IA | TYPE_EMERGENCY | TYPE_MCX
- | TYPE_XCAP | TYPE_VSIM | TYPE_BIP | TYPE_ENTERPRISE | TYPE_RCS)) == 0
+ | TYPE_XCAP | TYPE_VSIM | TYPE_BIP | TYPE_ENTERPRISE | TYPE_RCS | TYPE_OEM_PAID
+ | TYPE_OEM_PRIVATE)) == 0
|| TextUtils.isEmpty(mApnName) || TextUtils.isEmpty(mEntryName)) {
return null;
}
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 4eefaaca71f4..bd5c7597ba14 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -1113,6 +1113,12 @@ public final class SatelliteManager {
* @hide
*/
public static final int DATAGRAM_TYPE_SMS = 6;
+ /**
+ * Datagram type indicating that the message to be sent is an SMS checking
+ * for pending incoming SMS.
+ * @hide
+ */
+ public static final int DATAGRAM_TYPE_CHECK_PENDING_INCOMING_SMS = 7;
/** @hide */
@IntDef(prefix = "DATAGRAM_TYPE_", value = {
@@ -1122,7 +1128,8 @@ public final class SatelliteManager {
DATAGRAM_TYPE_KEEP_ALIVE,
DATAGRAM_TYPE_LAST_SOS_MESSAGE_STILL_NEED_HELP,
DATAGRAM_TYPE_LAST_SOS_MESSAGE_NO_HELP_NEEDED,
- DATAGRAM_TYPE_SMS
+ DATAGRAM_TYPE_SMS,
+ DATAGRAM_TYPE_CHECK_PENDING_INCOMING_SMS
})
@Retention(RetentionPolicy.SOURCE)
public @interface DatagramType {}
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index e57c207a0b3e..7f25ef25c9ac 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -3017,6 +3017,14 @@ interface ITelephony {
boolean setSatelliteListeningTimeoutDuration(in long timeoutMillis);
/**
+ * This API can be used by only CTS to control ingoring cellular service state event.
+ *
+ * @param enabled Whether to enable boolean config.
+ * @return {@code true} if the value is set successfully, {@code false} otherwise.
+ */
+ boolean setSatelliteIgnoreCellularServiceState(in boolean enabled);
+
+ /**
* This API can be used by only CTS to update satellite pointing UI app package and class names.
*
* @param packageName The package name of the satellite pointing UI app.
@@ -3409,4 +3417,20 @@ interface ITelephony {
* @hide
*/
boolean setSatelliteSubscriberIdListChangedIntentComponent(in String name);
+
+ /**
+ * This API can be used by only CTS to override the Euicc UI component.
+ *
+ * @param componentName ui component to be launched for testing
+ * @hide
+ */
+ void setTestEuiccUiComponent(in ComponentName componentName);
+
+ /**
+ * This API can be used by only CTS to retrieve the Euicc UI component.
+ *
+ * @return The Euicc UI component for testing.
+ * @hide
+ */
+ ComponentName getTestEuiccUiComponent();
}
diff --git a/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml b/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml
index 82de070921f0..8b65efdfb5f9 100644
--- a/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml
@@ -12,6 +12,10 @@
<option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
+ <!-- Turns off Wi-fi -->
+ <option name="wifi" value="off"/>
+ <!-- Turns off Bluetooth -->
+ <option name="bluetooth" value="off"/>
<!-- prevents the phone from restarting -->
<option name="force-skip-system-props" value="true"/>
<!-- set WM tracing verbose level to all -->
diff --git a/tests/FlickerTests/AppClose/AndroidTestTemplate.xml b/tests/FlickerTests/AppClose/AndroidTestTemplate.xml
index 4ffb11ab92ae..3382c1e227b3 100644
--- a/tests/FlickerTests/AppClose/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/AppClose/AndroidTestTemplate.xml
@@ -12,6 +12,10 @@
<option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
+ <!-- Turns off Wi-fi -->
+ <option name="wifi" value="off"/>
+ <!-- Turns off Bluetooth -->
+ <option name="bluetooth" value="off"/>
<!-- prevents the phone from restarting -->
<option name="force-skip-system-props" value="true"/>
<!-- set WM tracing verbose level to all -->
diff --git a/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml b/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml
index 0fa4d07b2eca..e941e79faea3 100644
--- a/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml
@@ -12,6 +12,10 @@
<option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
+ <!-- Turns off Wi-fi -->
+ <option name="wifi" value="off"/>
+ <!-- Turns off Bluetooth -->
+ <option name="bluetooth" value="off"/>
<!-- prevents the phone from restarting -->
<option name="force-skip-system-props" value="true"/>
<!-- set WM tracing verbose level to all -->
diff --git a/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml b/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml
index 4d9fefbc7d88..4e06dca17fe2 100644
--- a/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml
@@ -12,6 +12,10 @@
<option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
+ <!-- Turns off Wi-fi -->
+ <option name="wifi" value="off"/>
+ <!-- Turns off Bluetooth -->
+ <option name="bluetooth" value="off"/>
<!-- prevents the phone from restarting -->
<option name="force-skip-system-props" value="true"/>
<!-- set WM tracing verbose level to all -->
diff --git a/tests/FlickerTests/IME/AndroidTestTemplate.xml b/tests/FlickerTests/IME/AndroidTestTemplate.xml
index b879c54dcab3..0cadd68597b6 100644
--- a/tests/FlickerTests/IME/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/IME/AndroidTestTemplate.xml
@@ -12,6 +12,10 @@
<option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
+ <!-- Turns off Wi-fi -->
+ <option name="wifi" value="off"/>
+ <!-- Turns off Bluetooth -->
+ <option name="bluetooth" value="off"/>
<!-- enable AOD -->
<option name="set-secure-setting" key="doze_always_on" value="1" />
<!-- prevents the phone from restarting -->
diff --git a/tests/FlickerTests/Notification/AndroidTestTemplate.xml b/tests/FlickerTests/Notification/AndroidTestTemplate.xml
index 04b312a896b9..f32e8bed85ef 100644
--- a/tests/FlickerTests/Notification/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/Notification/AndroidTestTemplate.xml
@@ -12,6 +12,10 @@
<option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
+ <!-- Turns off Wi-fi -->
+ <option name="wifi" value="off"/>
+ <!-- Turns off Bluetooth -->
+ <option name="bluetooth" value="off"/>
<!-- prevents the phone from restarting -->
<option name="force-skip-system-props" value="true"/>
<!-- set WM tracing verbose level to all -->
diff --git a/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml b/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml
index 8acdabc2337d..68ae4f1f7f4f 100644
--- a/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml
@@ -12,6 +12,10 @@
<option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
+ <!-- Turns off Wi-fi -->
+ <option name="wifi" value="off"/>
+ <!-- Turns off Bluetooth -->
+ <option name="bluetooth" value="off"/>
<!-- prevents the phone from restarting -->
<option name="force-skip-system-props" value="true"/>
<!-- set WM tracing verbose level to all -->
diff --git a/tests/FlickerTests/Rotation/AndroidTestTemplate.xml b/tests/FlickerTests/Rotation/AndroidTestTemplate.xml
index 91ece214aad5..ec186723b4a4 100644
--- a/tests/FlickerTests/Rotation/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/Rotation/AndroidTestTemplate.xml
@@ -12,6 +12,10 @@
<option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on"/>
+ <!-- Turns off Wi-fi -->
+ <option name="wifi" value="off"/>
+ <!-- Turns off Bluetooth -->
+ <option name="bluetooth" value="off"/>
<!-- prevents the phone from restarting -->
<option name="force-skip-system-props" value="true"/>
<!-- set WM tracing verbose level to all -->
diff --git a/tests/broadcasts/unit/TEST_MAPPING b/tests/broadcasts/unit/TEST_MAPPING
index 0e824c54e92e..8919fdcd7a3f 100644
--- a/tests/broadcasts/unit/TEST_MAPPING
+++ b/tests/broadcasts/unit/TEST_MAPPING
@@ -1,5 +1,5 @@
{
- "postsubmit": [
+ "presubmit": [
{
"name": "BroadcastUnitTests",
"options": [
diff --git a/tests/testables/Android.bp b/tests/testables/Android.bp
index 7596ee722d01..f2111856c666 100644
--- a/tests/testables/Android.bp
+++ b/tests/testables/Android.bp
@@ -25,7 +25,10 @@ package {
java_library {
name: "testables",
- srcs: ["src/**/*.java"],
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
libs: [
"android.test.runner.stubs.system",
"android.test.mock.stubs.system",
diff --git a/tests/testables/src/android/testing/TestWithLooperRule.java b/tests/testables/src/android/testing/TestWithLooperRule.java
index 37b39c314e53..10df17f991d3 100644
--- a/tests/testables/src/android/testing/TestWithLooperRule.java
+++ b/tests/testables/src/android/testing/TestWithLooperRule.java
@@ -34,13 +34,13 @@ import java.util.List;
* Looper for the Statement.
*/
public class TestWithLooperRule implements MethodRule {
-
/*
* This rule requires to be the inner most Rule, so the next statement is RunAfters
* instead of another rule. You can set it by '@Rule(order = Integer.MAX_VALUE)'
*/
@Override
public Statement apply(Statement base, FrameworkMethod method, Object target) {
+
// getting testRunner check, if AndroidTestingRunning then we skip this rule
RunWith runWithAnnotation = target.getClass().getAnnotation(RunWith.class);
if (runWithAnnotation != null) {
@@ -97,6 +97,9 @@ public class TestWithLooperRule implements MethodRule {
case "InvokeParameterizedMethod":
this.wrapFieldMethodFor(next, "frameworkMethod", method, target);
return;
+ case "ExpectException":
+ next = this.getNextStatement(next, "next");
+ break;
default:
throw new Exception(
String.format("Unexpected Statement received: [%s]",
diff --git a/tests/testables/tests/Android.bp b/tests/testables/tests/Android.bp
index 1eb36fa5f908..c23f41a6c3d4 100644
--- a/tests/testables/tests/Android.bp
+++ b/tests/testables/tests/Android.bp
@@ -34,6 +34,7 @@ android_test {
"androidx.core_core-animation",
"androidx.core_core-ktx",
"androidx.test.rules",
+ "androidx.test.ext.junit",
"hamcrest-library",
"mockito-target-inline-minus-junit4",
"testables",
diff --git a/tests/testables/tests/src/android/testing/TestableLooperJUnit4Test.java b/tests/testables/tests/src/android/testing/TestableLooperJUnit4Test.java
new file mode 100644
index 000000000000..b7d5e0e12942
--- /dev/null
+++ b/tests/testables/tests/src/android/testing/TestableLooperJUnit4Test.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.testing;
+
+import android.testing.TestableLooper.RunWithLooper;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test that TestableLooper now handles expected exceptions in tests
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+@RunWithLooper
+public class TestableLooperJUnit4Test {
+ @Rule
+ public final TestWithLooperRule mTestWithLooperRule = new TestWithLooperRule();
+
+ @Test(expected = Exception.class)
+ public void testException() throws Exception {
+ throw new Exception("this exception is expected");
+ }
+}
+